package queryapi import ( "encoding/json" "testing" "time" "queryorchestration/internal/database/repository" "queryorchestration/internal/eula" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestConvertAgreementToAPI_NonEmailIdentifier verifies that agreements stored with // non-email identifiers (username or subject ID fallbacks) serialize to JSON without error. // This is the fix for bug #1: non-email userEmail values caused 500 on GET /admin/eula/agreements. func TestConvertAgreementToAPI_NonEmailIdentifier(t *testing.T) { tests := []struct { name string userEmail string }{ {name: "valid email", userEmail: "user@example.com"}, {name: "username fallback", userEmail: "accessuser"}, {name: "subject ID fallback", userEmail: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}, {name: "empty string", userEmail: ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { row := &repository.ListEulaAgreementsRow{ ID: uuid.New(), Cognitosubjectid: "sub-123", Useremail: tt.userEmail, Eulaversionid: uuid.New(), Agreedat: pgtype.Timestamptz{Time: time.Now(), Valid: true}, Agreedfromip: "192.168.1.1", Eulaversionstring: "1.0", } result := convertAgreementToAPI(row) assert.Equal(t, tt.userEmail, string(result.UserEmail)) // The critical check: JSON marshal must not fail data, err := json.Marshal(result) require.NoError(t, err, "JSON marshal failed for userEmail=%q", tt.userEmail) assert.Contains(t, string(data), `"userEmail"`) }) } } // TestConvertVersionToAPI_NonEmailCreatedBy verifies that versions with non-email // createdBy/activatedBy values serialize to JSON without error. // This is the fix for bug #2: non-email createdBy/activatedBy caused 500 on GET /admin/eula. func TestConvertVersionToAPI_NonEmailCreatedBy(t *testing.T) { tests := []struct { name string createdBy string activatedBy *string }{ {name: "valid emails", createdBy: "admin@example.com", activatedBy: strPtr("admin@example.com")}, {name: "username createdBy", createdBy: "adminuser", activatedBy: nil}, {name: "subject createdBy", createdBy: "a1b2c3d4-e5f6-7890", activatedBy: nil}, {name: "username activatedBy", createdBy: "admin@test.com", activatedBy: strPtr("adminuser")}, {name: "both non-email", createdBy: "sysadmin", activatedBy: strPtr("opsuser")}, {name: "empty createdBy", createdBy: "", activatedBy: nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { version := &repository.Eulaversion{ ID: uuid.New(), Version: "1.0", Title: "Test EULA", Content: "# Test content", Createdat: pgtype.Timestamptz{Time: time.Now(), Valid: true}, Createdby: tt.createdBy, Iscurrent: true, } if tt.activatedBy != nil { version.Activatedat = pgtype.Timestamptz{Time: time.Now(), Valid: true} version.Activatedby = tt.activatedBy } result := convertVersionToAPI(version) assert.Equal(t, tt.createdBy, string(result.CreatedBy)) // The critical check: JSON marshal must not fail data, err := json.Marshal(result) require.NoError(t, err, "JSON marshal failed for createdBy=%q, activatedBy=%v", tt.createdBy, tt.activatedBy) assert.Contains(t, string(data), `"createdBy"`) if tt.activatedBy != nil { assert.Contains(t, string(data), `"activatedBy"`) } }) } } // TestConvertComplianceReportToAPI_NonEmailIdentifiers verifies that compliance reports // with non-email user identifiers serialize to JSON without error. // This is the fix for bug #3: non-email currentEmail/email from Cognito caused 500 // on GET /admin/eula/compliance. func TestConvertComplianceReportToAPI_NonEmailIdentifiers(t *testing.T) { tests := []struct { name string currentEmail string email *string }{ {name: "valid emails", currentEmail: "user@example.com", email: strPtr("user@example.com")}, {name: "username currentEmail", currentEmail: "accessuser", email: strPtr("accessuser")}, {name: "empty currentEmail", currentEmail: "", email: nil}, {name: "subject ID currentEmail", currentEmail: "a1b2c3d4-e5f6-7890", email: strPtr("a1b2c3d4-e5f6-7890")}, {name: "mixed email and username", currentEmail: "user@example.com", email: strPtr("oldusername")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { agreedAt := time.Now() ip := "10.0.0.1" user := eula.ComplianceReportUser{ CognitoSubjectID: "sub-" + uuid.New().String()[:8], CurrentEmail: tt.currentEmail, Agreed: tt.email != nil, } if tt.email != nil { user.Email = tt.email user.AgreedAt = &agreedAt user.AgreedFromIP = &ip } result := &eula.ComplianceReportResult{ Version: &repository.Eulaversion{ ID: uuid.New(), Version: "1.0", Title: "Test EULA", Content: "# Test", Createdat: pgtype.Timestamptz{Time: time.Now(), Valid: true}, Createdby: "admin@test.com", Iscurrent: true, }, Summary: eula.ComplianceReportSummary{ TotalUsers: 1, AgreedCount: 1, NotAgreedCount: 0, CompliancePercentage: 100.0, }, Users: []eula.ComplianceReportUser{user}, Page: 1, PageSize: 50, TotalPages: 1, TotalItems: 1, } report := convertComplianceReportToAPI(result) assert.Equal(t, tt.currentEmail, string(report.Users[0].CurrentEmail)) // The critical check: JSON marshal must not fail data, err := json.Marshal(report) require.NoError(t, err, "JSON marshal failed for currentEmail=%q, email=%v", tt.currentEmail, tt.email) assert.Contains(t, string(data), `"currentEmail"`) }) } } // strPtr returns a pointer to the given string. func strPtr(s string) *string { return &s }