| /* |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package tasks |
| |
| import ( |
| "testing" |
| "time" |
| |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/mock" |
| |
| "github.com/apache/incubator-devlake/plugins/q_dev/models" |
| ) |
| |
| // Mock Identity Client for testing |
| type MockIdentityClient struct { |
| mock.Mock |
| } |
| |
| func (m *MockIdentityClient) ResolveUserDisplayName(userId string) (string, error) { |
| args := m.Called(userId) |
| return args.String(0), args.Error(1) |
| } |
| |
| // Ensure MockIdentityClient implements UserDisplayNameResolver |
| var _ UserDisplayNameResolver = (*MockIdentityClient)(nil) |
| |
| // MockLogger is a mock implementation of the logger interface for testing |
| type MockLogger struct { |
| mock.Mock |
| } |
| |
| func (m *MockLogger) Debug(format string, args ...interface{}) { |
| m.Called(format, args) |
| } |
| |
| func TestCreateUserDataWithDisplayName_Success(t *testing.T) { |
| headers := []string{"UserId", "Date", "CodeReview_FindingsCount", "Inline_AcceptanceCount"} |
| record := []string{"user-123", "2025-06-23", "5", "10"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "user-123").Return("John Doe", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| assert.Equal(t, "user-123", userData.UserId) |
| assert.Equal(t, "John Doe", userData.DisplayName) |
| assert.Equal(t, uint64(1), userData.ConnectionId) |
| assert.Equal(t, 5, userData.CodeReview_FindingsCount) |
| assert.Equal(t, 10, userData.Inline_AcceptanceCount) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_FallbackToUUID(t *testing.T) { |
| headers := []string{"UserId", "Date"} |
| record := []string{"user-456", "2025-06-23"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "user-456").Return("user-456", assert.AnError) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Failed to resolve display name for user %s: %v", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| assert.Equal(t, "user-456", userData.UserId) |
| assert.Equal(t, "user-456", userData.DisplayName) // Should fallback to UUID |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_NoIdentityClient(t *testing.T) { |
| headers := []string{"UserId", "Date"} |
| record := []string{"user-789", "2025-06-23"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| assert.Equal(t, "user-789", userData.UserId) |
| assert.Equal(t, "user-789", userData.DisplayName) // Should use UUID when no client |
| } |
| |
| func TestCreateUserDataWithDisplayName_EmptyDisplayName(t *testing.T) { |
| headers := []string{"UserId", "Date"} |
| record := []string{"user-empty", "2025-06-23"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "user-empty").Return("", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| assert.Equal(t, "user-empty", userData.UserId) |
| assert.Equal(t, "user-empty", userData.DisplayName) // Should fallback when empty |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_AllExistingMetrics(t *testing.T) { |
| headers := []string{ |
| "UserId", "Date", "CodeReview_FindingsCount", "CodeReview_SucceededEventCount", |
| "InlineChat_AcceptanceEventCount", "InlineChat_AcceptedLineAdditions", |
| "InlineChat_AcceptedLineDeletions", "InlineChat_DismissalEventCount", |
| "InlineChat_DismissedLineAdditions", "InlineChat_DismissedLineDeletions", |
| "InlineChat_RejectedLineAdditions", "InlineChat_RejectedLineDeletions", |
| "InlineChat_RejectionEventCount", "InlineChat_TotalEventCount", |
| "Inline_AICodeLines", "Inline_AcceptanceCount", "Inline_SuggestionsCount", |
| } |
| record := []string{ |
| "test-user", "2025-06-23", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", |
| } |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 123, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "test-user").Return("Test User", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| |
| // Verify basic fields |
| assert.Equal(t, "test-user", userData.UserId) |
| assert.Equal(t, "Test User", userData.DisplayName) |
| assert.Equal(t, uint64(123), userData.ConnectionId) |
| |
| // Verify date parsing |
| expectedDate, _ := time.Parse("2006-01-02", "2025-06-23") |
| assert.Equal(t, expectedDate, userData.Date) |
| |
| // Verify all existing metric fields |
| assert.Equal(t, 1, userData.CodeReview_FindingsCount) |
| assert.Equal(t, 2, userData.CodeReview_SucceededEventCount) |
| assert.Equal(t, 3, userData.InlineChat_AcceptanceEventCount) |
| assert.Equal(t, 4, userData.InlineChat_AcceptedLineAdditions) |
| assert.Equal(t, 5, userData.InlineChat_AcceptedLineDeletions) |
| assert.Equal(t, 6, userData.InlineChat_DismissalEventCount) |
| assert.Equal(t, 7, userData.InlineChat_DismissedLineAdditions) |
| assert.Equal(t, 8, userData.InlineChat_DismissedLineDeletions) |
| assert.Equal(t, 9, userData.InlineChat_RejectedLineAdditions) |
| assert.Equal(t, 10, userData.InlineChat_RejectedLineDeletions) |
| assert.Equal(t, 11, userData.InlineChat_RejectionEventCount) |
| assert.Equal(t, 12, userData.InlineChat_TotalEventCount) |
| assert.Equal(t, 13, userData.Inline_AICodeLines) |
| assert.Equal(t, 14, userData.Inline_AcceptanceCount) |
| assert.Equal(t, 15, userData.Inline_SuggestionsCount) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_AllNewMetrics(t *testing.T) { |
| headers := []string{ |
| "UserId", "Date", |
| "Chat_AICodeLines", "Chat_MessagesInteracted", "Chat_MessagesSent", |
| "CodeFix_AcceptanceEventCount", "CodeFix_AcceptedLines", "CodeFix_GeneratedLines", "CodeFix_GenerationEventCount", |
| "CodeReview_FailedEventCount", |
| "Dev_AcceptanceEventCount", "Dev_AcceptedLines", "Dev_GeneratedLines", "Dev_GenerationEventCount", |
| "DocGeneration_AcceptedFileUpdates", "DocGeneration_AcceptedFilesCreations", "DocGeneration_AcceptedLineAdditions", |
| "DocGeneration_AcceptedLineUpdates", "DocGeneration_EventCount", "DocGeneration_RejectedFileCreations", |
| "DocGeneration_RejectedFileUpdates", "DocGeneration_RejectedLineAdditions", "DocGeneration_RejectedLineUpdates", |
| "TestGeneration_AcceptedLines", "TestGeneration_AcceptedTests", "TestGeneration_EventCount", |
| "TestGeneration_GeneratedLines", "TestGeneration_GeneratedTests", |
| "Transformation_EventCount", "Transformation_LinesGenerated", "Transformation_LinesIngested", |
| } |
| |
| record := []string{ |
| "test-user", "2025-06-23", |
| "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", |
| "111", "112", "113", "114", "115", "116", "117", "118", "119", "120", |
| "121", "122", "123", "124", "125", "126", "127", "128", "129", |
| } |
| |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 123, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "test-user").Return("Test User", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| |
| // Verify basic fields |
| assert.Equal(t, "test-user", userData.UserId) |
| assert.Equal(t, "Test User", userData.DisplayName) |
| |
| // Verify all new metric fields |
| assert.Equal(t, 101, userData.Chat_AICodeLines) |
| assert.Equal(t, 102, userData.Chat_MessagesInteracted) |
| assert.Equal(t, 103, userData.Chat_MessagesSent) |
| assert.Equal(t, 104, userData.CodeFix_AcceptanceEventCount) |
| assert.Equal(t, 105, userData.CodeFix_AcceptedLines) |
| assert.Equal(t, 106, userData.CodeFix_GeneratedLines) |
| assert.Equal(t, 107, userData.CodeFix_GenerationEventCount) |
| assert.Equal(t, 108, userData.CodeReview_FailedEventCount) |
| assert.Equal(t, 109, userData.Dev_AcceptanceEventCount) |
| assert.Equal(t, 110, userData.Dev_AcceptedLines) |
| assert.Equal(t, 111, userData.Dev_GeneratedLines) |
| assert.Equal(t, 112, userData.Dev_GenerationEventCount) |
| assert.Equal(t, 113, userData.DocGeneration_AcceptedFileUpdates) |
| assert.Equal(t, 114, userData.DocGeneration_AcceptedFilesCreations) |
| assert.Equal(t, 115, userData.DocGeneration_AcceptedLineAdditions) |
| assert.Equal(t, 116, userData.DocGeneration_AcceptedLineUpdates) |
| assert.Equal(t, 117, userData.DocGeneration_EventCount) |
| assert.Equal(t, 118, userData.DocGeneration_RejectedFileCreations) |
| assert.Equal(t, 119, userData.DocGeneration_RejectedFileUpdates) |
| assert.Equal(t, 120, userData.DocGeneration_RejectedLineAdditions) |
| assert.Equal(t, 121, userData.DocGeneration_RejectedLineUpdates) |
| assert.Equal(t, 122, userData.TestGeneration_AcceptedLines) |
| assert.Equal(t, 123, userData.TestGeneration_AcceptedTests) |
| assert.Equal(t, 124, userData.TestGeneration_EventCount) |
| assert.Equal(t, 125, userData.TestGeneration_GeneratedLines) |
| assert.Equal(t, 126, userData.TestGeneration_GeneratedTests) |
| assert.Equal(t, 127, userData.Transformation_EventCount) |
| assert.Equal(t, 128, userData.Transformation_LinesGenerated) |
| assert.Equal(t, 129, userData.Transformation_LinesIngested) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_MissingMetrics(t *testing.T) { |
| // Only provide a few metrics in the CSV |
| headers := []string{"UserId", "Date", "CodeReview_FindingsCount", "Chat_AICodeLines"} |
| record := []string{"test-user", "2025-06-23", "42", "99"} |
| |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 123, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "test-user").Return("Test User", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| |
| // Verify provided metrics are set correctly |
| assert.Equal(t, 42, userData.CodeReview_FindingsCount) |
| assert.Equal(t, 99, userData.Chat_AICodeLines) |
| |
| // Verify missing metrics are set to 0 |
| assert.Equal(t, 0, userData.CodeReview_SucceededEventCount) |
| assert.Equal(t, 0, userData.InlineChat_AcceptanceEventCount) |
| assert.Equal(t, 0, userData.Chat_MessagesInteracted) |
| assert.Equal(t, 0, userData.TestGeneration_AcceptedTests) |
| assert.Equal(t, 0, userData.Transformation_LinesIngested) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_InvalidMetricValues(t *testing.T) { |
| headers := []string{ |
| "UserId", "Date", "CodeReview_FindingsCount", "Chat_AICodeLines", |
| "InlineChat_AcceptanceEventCount", "TestGeneration_AcceptedTests", |
| } |
| record := []string{"test-user", "2025-06-23", "42", "not-a-number", "abc", ""} |
| |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 123, |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "test-user").Return("Test User", nil) |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, userData) |
| |
| // Verify valid metric is set correctly |
| assert.Equal(t, 42, userData.CodeReview_FindingsCount) |
| |
| // Verify invalid metrics are set to 0 |
| assert.Equal(t, 0, userData.Chat_AICodeLines) |
| assert.Equal(t, 0, userData.InlineChat_AcceptanceEventCount) |
| assert.Equal(t, 0, userData.TestGeneration_AcceptedTests) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserDataWithDisplayName_MissingUserId(t *testing.T) { |
| headers := []string{"Date", "CodeReview_FindingsCount"} |
| record := []string{"2025-06-23", "5"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.Error(t, err) |
| assert.Nil(t, userData) |
| assert.Contains(t, err.Error(), "UserId not found") |
| } |
| |
| func TestCreateUserDataWithDisplayName_MissingDate(t *testing.T) { |
| headers := []string{"UserId", "CodeReview_FindingsCount"} |
| record := []string{"user-123", "5"} |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| } |
| |
| mockLogger := &MockLogger{} |
| // Add expectations for Debug calls |
| mockLogger.On("Debug", "Mapping header[%d]: '%s' -> '%s'", mock.Anything).Return() |
| mockLogger.On("Debug", "Also adding trimmed header: '%s'", mock.Anything).Return() |
| |
| userData, err := createUserDataWithDisplayName(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.Error(t, err) |
| assert.Nil(t, userData) |
| assert.Contains(t, err.Error(), "Date not found") |
| } |
| |
| func TestParseDate(t *testing.T) { |
| testCases := []struct { |
| dateStr string |
| expectedDate time.Time |
| expectError bool |
| }{ |
| {"2025-07-10", time.Date(2025, 7, 10, 0, 0, 0, 0, time.UTC), false}, |
| {"2025/07/10", time.Date(2025, 7, 10, 0, 0, 0, 0, time.UTC), false}, |
| {"07/10/2025", time.Date(2025, 7, 10, 0, 0, 0, 0, time.UTC), false}, |
| {"07-10-2025", time.Date(2025, 7, 10, 0, 0, 0, 0, time.UTC), false}, |
| {"2025-07-10T15:04:05Z", time.Date(2025, 7, 10, 15, 4, 5, 0, time.UTC), false}, |
| {"invalid-date", time.Time{}, true}, |
| } |
| |
| for _, tc := range testCases { |
| date, err := parseDate(tc.dateStr) |
| |
| if tc.expectError { |
| assert.Error(t, err) |
| } else { |
| assert.NoError(t, err) |
| assert.Equal(t, tc.expectedDate, date) |
| } |
| } |
| } |
| |
| func TestDetectUserReportFormat(t *testing.T) { |
| // New format: contains Client_Type |
| assert.True(t, detectUserReportFormat([]string{"UserId", "Date", "Client_Type", "Credits_Used"})) |
| // New format: contains Credits_Used |
| assert.True(t, detectUserReportFormat([]string{"UserId", "Date", "Credits_Used", "Total_Messages"})) |
| // Old format: code-level metrics |
| assert.False(t, detectUserReportFormat([]string{"UserId", "Date", "Chat_AICodeLines", "Inline_AICodeLines"})) |
| // Old format: no new-format indicators |
| assert.False(t, detectUserReportFormat([]string{"UserId", "Date", "CodeReview_FindingsCount"})) |
| // Empty headers |
| assert.False(t, detectUserReportFormat([]string{})) |
| // Whitespace-padded header still detected |
| assert.True(t, detectUserReportFormat([]string{"UserId", " Client_Type ", "Date"})) |
| } |
| |
| func TestCreateUserReportData_Success(t *testing.T) { |
| headers := []string{ |
| "UserId", "Date", "Client_Type", "Subscription_Tier", "ProfileId", |
| "Chat_Conversations", "Credits_Used", "Overage_Cap", "Overage_Credits_Used", |
| "Overage_Enabled", "Total_Messages", |
| } |
| record := []string{ |
| "user-abc", "2026-01-15", "KIRO_IDE", "Pro", "profile-xyz", |
| "12", "45.5", "100.0", "5.25", |
| "true", "87", |
| } |
| fileMeta := &models.QDevS3FileMeta{ |
| ConnectionId: 1, |
| ScopeId: "scope-1", |
| } |
| |
| mockIdentityClient := &MockIdentityClient{} |
| mockIdentityClient.On("ResolveUserDisplayName", "user-abc").Return("Alice Bob", nil) |
| |
| mockLogger := &MockLogger{} |
| mockLogger.On("Debug", mock.Anything, mock.Anything).Return() |
| |
| report, err := createUserReportData(mockLogger, headers, record, fileMeta, mockIdentityClient) |
| |
| assert.NoError(t, err) |
| assert.NotNil(t, report) |
| assert.Equal(t, "user-abc", report.UserId) |
| assert.Equal(t, "Alice Bob", report.DisplayName) |
| assert.Equal(t, uint64(1), report.ConnectionId) |
| assert.Equal(t, "scope-1", report.ScopeId) |
| assert.Equal(t, "KIRO_IDE", report.ClientType) |
| assert.Equal(t, "Pro", report.SubscriptionTier) |
| assert.Equal(t, "profile-xyz", report.ProfileId) |
| assert.Equal(t, 12, report.ChatConversations) |
| assert.Equal(t, 45.5, report.CreditsUsed) |
| assert.Equal(t, 100.0, report.OverageCap) |
| assert.Equal(t, 5.25, report.OverageCreditsUsed) |
| assert.True(t, report.OverageEnabled) |
| assert.Equal(t, 87, report.TotalMessages) |
| |
| expectedDate, _ := time.Parse("2006-01-02", "2026-01-15") |
| assert.Equal(t, expectedDate, report.Date) |
| |
| mockIdentityClient.AssertExpectations(t) |
| } |
| |
| func TestCreateUserReportData_MissingUserId(t *testing.T) { |
| headers := []string{"Date", "Client_Type", "Credits_Used"} |
| record := []string{"2026-01-15", "KIRO_IDE", "10.0"} |
| fileMeta := &models.QDevS3FileMeta{ConnectionId: 1} |
| |
| mockLogger := &MockLogger{} |
| mockLogger.On("Debug", mock.Anything, mock.Anything).Return() |
| |
| report, err := createUserReportData(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.Error(t, err) |
| assert.Nil(t, report) |
| assert.Contains(t, err.Error(), "UserId not found") |
| } |
| |
| func TestCreateUserReportData_MissingDate(t *testing.T) { |
| headers := []string{"UserId", "Client_Type", "Credits_Used"} |
| record := []string{"user-abc", "KIRO_IDE", "10.0"} |
| fileMeta := &models.QDevS3FileMeta{ConnectionId: 1} |
| |
| mockLogger := &MockLogger{} |
| mockLogger.On("Debug", mock.Anything, mock.Anything).Return() |
| |
| report, err := createUserReportData(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.Error(t, err) |
| assert.Nil(t, report) |
| assert.Contains(t, err.Error(), "Date not found") |
| } |
| |
| func TestCreateUserReportData_OverageDisabled(t *testing.T) { |
| headers := []string{"UserId", "Date", "Overage_Enabled", "Credits_Used"} |
| record := []string{"user-abc", "2026-01-15", "false", "10.0"} |
| fileMeta := &models.QDevS3FileMeta{ConnectionId: 1} |
| |
| mockLogger := &MockLogger{} |
| mockLogger.On("Debug", mock.Anything, mock.Anything).Return() |
| |
| report, err := createUserReportData(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.NoError(t, err) |
| assert.False(t, report.OverageEnabled) |
| } |
| |
| func TestCreateUserReportData_InvalidNumericValues(t *testing.T) { |
| headers := []string{"UserId", "Date", "Credits_Used", "Chat_Conversations", "Total_Messages"} |
| record := []string{"user-abc", "2026-01-15", "not-a-float", "not-an-int", ""} |
| fileMeta := &models.QDevS3FileMeta{ConnectionId: 1} |
| |
| mockLogger := &MockLogger{} |
| mockLogger.On("Debug", mock.Anything, mock.Anything).Return() |
| |
| report, err := createUserReportData(mockLogger, headers, record, fileMeta, nil) |
| |
| assert.NoError(t, err) |
| assert.Equal(t, float64(0), report.CreditsUsed) |
| assert.Equal(t, 0, report.ChatConversations) |
| assert.Equal(t, 0, report.TotalMessages) |
| } |
| |
| func TestParseFloat(t *testing.T) { |
| fieldMap := map[string]string{ |
| "ValidFloat": "3.14", |
| "ZeroFloat": "0", |
| "NegativeFloat": "-2.5", |
| "IntegerValue": "42", |
| "InvalidFloat": "not-a-number", |
| "EmptyString": "", |
| "Whitespace": " 1.5 ", |
| } |
| |
| assert.Equal(t, 3.14, parseFloat(fieldMap, "ValidFloat")) |
| assert.Equal(t, float64(0), parseFloat(fieldMap, "ZeroFloat")) |
| assert.Equal(t, -2.5, parseFloat(fieldMap, "NegativeFloat")) |
| assert.Equal(t, float64(42), parseFloat(fieldMap, "IntegerValue")) |
| assert.Equal(t, float64(0), parseFloat(fieldMap, "InvalidFloat")) |
| assert.Equal(t, float64(0), parseFloat(fieldMap, "EmptyString")) |
| assert.Equal(t, 1.5, parseFloat(fieldMap, "Whitespace")) |
| assert.Equal(t, float64(0), parseFloat(fieldMap, "NonExistentField")) |
| } |
| |
| func TestParseBool(t *testing.T) { |
| fieldMap := map[string]string{ |
| "TrueValue": "true", |
| "TrueUpper": "True", |
| "TrueOne": "1", |
| "TrueYes": "yes", |
| "FalseValue": "false", |
| "FalseZero": "0", |
| "EmptyString": "", |
| "InvalidBool": "maybe", |
| "WhitespaceVal": " true ", |
| } |
| |
| assert.True(t, parseBool(fieldMap, "TrueValue")) |
| assert.True(t, parseBool(fieldMap, "TrueUpper")) |
| assert.True(t, parseBool(fieldMap, "TrueOne")) |
| assert.True(t, parseBool(fieldMap, "TrueYes")) |
| assert.False(t, parseBool(fieldMap, "FalseValue")) |
| assert.False(t, parseBool(fieldMap, "FalseZero")) |
| assert.False(t, parseBool(fieldMap, "EmptyString")) |
| assert.False(t, parseBool(fieldMap, "InvalidBool")) |
| assert.True(t, parseBool(fieldMap, "WhitespaceVal")) |
| assert.False(t, parseBool(fieldMap, "NonExistentField")) |
| } |
| |
| func TestParseInt(t *testing.T) { |
| fieldMap := map[string]string{ |
| "ValidInt": "42", |
| "ZeroInt": "0", |
| "NegativeInt": "-10", |
| "InvalidInt": "not-a-number", |
| "EmptyString": "", |
| } |
| |
| assert.Equal(t, 42, parseInt(fieldMap, "ValidInt")) |
| assert.Equal(t, 0, parseInt(fieldMap, "ZeroInt")) |
| assert.Equal(t, -10, parseInt(fieldMap, "NegativeInt")) |
| assert.Equal(t, 0, parseInt(fieldMap, "InvalidInt")) |
| assert.Equal(t, 0, parseInt(fieldMap, "EmptyString")) |
| assert.Equal(t, 0, parseInt(fieldMap, "NonExistentField")) |
| } |