Files
query-orchestration/internal/eula/service_test.go
T

1393 lines
42 KiB
Go
Raw Normal View History

package eula_test
import (
"os"
"testing"
"time"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/eula"
"queryorchestration/internal/serviceconfig"
"queryorchestration/internal/test"
"queryorchestration/internal/usermanagement"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestConfig embeds BaseConfig for test database setup.
type TestConfig struct {
serviceconfig.BaseConfig
}
// ptrTime is a helper to create a pointer to a time.Time value.
func ptrTime(t time.Time) *time.Time {
return &t
}
func TestCreateVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("create version successfully", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "1.0.0-test-" + uuid.New().String()[:8],
Title: "Test EULA v1.0",
Content: "# Terms and Conditions\n\nThis is the EULA content.",
EffectiveDate: ptrTime(time.Now().Add(24 * time.Hour)),
CreatedBy: "admin@test.com",
}
result, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, input.Version, result.Version)
assert.Equal(t, input.Title, result.Title)
assert.Equal(t, input.Content, result.Content)
assert.Equal(t, input.CreatedBy, result.Createdby)
assert.False(t, result.Iscurrent)
assert.NotEqual(t, uuid.Nil, result.ID)
})
t.Run("reject empty version string", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "",
Title: "Test EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err := svc.CreateVersion(ctx, input)
require.Error(t, err)
assert.Contains(t, err.Error(), "version string cannot be empty")
})
t.Run("reject empty title", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "2.0.0",
Title: "",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err := svc.CreateVersion(ctx, input)
require.Error(t, err)
assert.Contains(t, err.Error(), "title cannot be empty")
})
t.Run("reject empty content", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "2.0.0",
Title: "Title",
Content: "",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err := svc.CreateVersion(ctx, input)
require.Error(t, err)
assert.Contains(t, err.Error(), "content cannot be empty")
})
t.Run("reject empty createdBy", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "2.0.0",
Title: "Title",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "",
}
_, err := svc.CreateVersion(ctx, input)
require.Error(t, err)
assert.Contains(t, err.Error(), "createdBy cannot be empty")
})
t.Run("reject duplicate version string", func(t *testing.T) {
uniqueVersion := "dup-" + uuid.New().String()[:8]
input1 := &eula.CreateVersionInput{
Version: uniqueVersion,
Title: "First EULA",
Content: "Content 1",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err := svc.CreateVersion(ctx, input1)
require.NoError(t, err)
input2 := &eula.CreateVersionInput{
Version: uniqueVersion,
Title: "Second EULA",
Content: "Content 2",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err = svc.CreateVersion(ctx, input2)
require.Error(t, err)
})
}
func TestGetVersionByID(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("get existing version", func(t *testing.T) {
// Create a version first
input := &eula.CreateVersionInput{
Version: "getbyid-" + uuid.New().String()[:8],
Title: "Test EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
created, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
// Retrieve it
result, err := svc.GetVersionByID(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, created.ID, result.ID)
assert.Equal(t, created.Version, result.Version)
})
t.Run("return ErrVersionNotFound for non-existent ID", func(t *testing.T) {
nonExistentID := uuid.New()
_, err := svc.GetVersionByID(ctx, nonExistentID)
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrVersionNotFound)
})
}
func TestListVersions(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create multiple versions
for i := 0; i < 5; i++ {
input := &eula.CreateVersionInput{
Version: "list-" + uuid.New().String()[:8],
Title: "List EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
_, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
}
t.Run("list with pagination", func(t *testing.T) {
result, err := svc.ListVersions(ctx, &eula.ListVersionsInput{
Page: 1,
PageSize: 3,
})
require.NoError(t, err)
assert.LessOrEqual(t, len(result.Versions), 3)
assert.GreaterOrEqual(t, result.Total, int64(5))
})
t.Run("apply default pagination values", func(t *testing.T) {
result, err := svc.ListVersions(ctx, &eula.ListVersionsInput{
Page: 0,
PageSize: 0,
})
require.NoError(t, err)
assert.LessOrEqual(t, len(result.Versions), 20)
})
t.Run("cap page size at 100", func(t *testing.T) {
result, err := svc.ListVersions(ctx, &eula.ListVersionsInput{
Page: 1,
PageSize: 500,
})
require.NoError(t, err)
assert.LessOrEqual(t, len(result.Versions), 100)
})
}
func TestUpdateVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("update title only", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "update-" + uuid.New().String()[:8],
Title: "Original Title",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
created, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
newTitle := "Updated Title"
updated, err := svc.UpdateVersion(ctx, created.ID, &eula.UpdateVersionInput{
Title: &newTitle,
})
require.NoError(t, err)
assert.Equal(t, newTitle, updated.Title)
assert.Equal(t, created.Content, updated.Content) // Content unchanged
})
t.Run("update effective date only", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "update2-" + uuid.New().String()[:8],
Title: "Title",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
created, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
newDate := time.Now().Add(48 * time.Hour)
updated, err := svc.UpdateVersion(ctx, created.ID, &eula.UpdateVersionInput{
EffectiveDate: &newDate,
})
require.NoError(t, err)
assert.WithinDuration(t, newDate, updated.Effectivedate.Time, time.Second)
assert.Equal(t, created.Title, updated.Title) // Title unchanged
})
t.Run("return error for non-existent version", func(t *testing.T) {
newTitle := "Updated Title"
_, err := svc.UpdateVersion(ctx, uuid.New(), &eula.UpdateVersionInput{
Title: &newTitle,
})
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrVersionNotFound)
})
}
func TestActivateVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("activate version successfully", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "activate-" + uuid.New().String()[:8],
Title: "Activate EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
created, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
assert.False(t, created.Iscurrent)
// Activate it
activated, err := svc.ActivateVersion(ctx, created.ID, "activator@test.com")
require.NoError(t, err)
assert.True(t, activated.Iscurrent)
assert.NotNil(t, activated.Activatedby)
assert.Equal(t, "activator@test.com", *activated.Activatedby)
assert.True(t, activated.Activatedat.Valid)
})
t.Run("activating new version deactivates previous", func(t *testing.T) {
// Create and activate first version
input1 := &eula.CreateVersionInput{
Version: "first-" + uuid.New().String()[:8],
Title: "First EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
first, err := svc.CreateVersion(ctx, input1)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, first.ID, "admin@test.com")
require.NoError(t, err)
// Create and activate second version
input2 := &eula.CreateVersionInput{
Version: "second-" + uuid.New().String()[:8],
Title: "Second EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
second, err := svc.CreateVersion(ctx, input2)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, second.ID, "admin@test.com")
require.NoError(t, err)
// Verify first is no longer current
firstAfter, err := svc.GetVersionByID(ctx, first.ID)
require.NoError(t, err)
assert.False(t, firstAfter.Iscurrent)
// Verify second is current
secondAfter, err := svc.GetVersionByID(ctx, second.ID)
require.NoError(t, err)
assert.True(t, secondAfter.Iscurrent)
})
t.Run("return error for non-existent version", func(t *testing.T) {
_, err := svc.ActivateVersion(ctx, uuid.New(), "admin@test.com")
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrVersionNotFound)
})
}
func TestGetCurrentVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("return current version after activation", func(t *testing.T) {
input := &eula.CreateVersionInput{
Version: "current-" + uuid.New().String()[:8],
Title: "Current EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
created, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, created.ID, "admin@test.com")
require.NoError(t, err)
current, err := svc.GetCurrentVersion(ctx)
require.NoError(t, err)
assert.Equal(t, created.ID, current.ID)
assert.True(t, current.Iscurrent)
})
}
func TestRecordAgreement(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create and activate a version for agreements
input := &eula.CreateVersionInput{
Version: "agree-" + uuid.New().String()[:8],
Title: "Agreement EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
t.Run("record agreement successfully", func(t *testing.T) {
userSubject := "user-" + uuid.New().String()[:8]
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: "user@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
result, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
assert.NotNil(t, result)
assert.False(t, result.AlreadyAgreed)
assert.Equal(t, userSubject, result.Agreement.Cognitosubjectid)
assert.Equal(t, version.ID, result.Agreement.Eulaversionid)
})
t.Run("idempotent - return existing agreement if already agreed", func(t *testing.T) {
userSubject := "idempotent-" + uuid.New().String()[:8]
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: "idempotent@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.2",
}
// First agreement
result1, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
assert.False(t, result1.AlreadyAgreed)
// Second agreement (same user, same version)
result2, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
assert.True(t, result2.AlreadyAgreed)
assert.Equal(t, result1.Agreement.ID, result2.Agreement.ID)
})
t.Run("reject empty cognitoSubjectID", func(t *testing.T) {
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: "",
UserEmail: "user@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.Error(t, err)
assert.Contains(t, err.Error(), "cognitoSubjectID cannot be empty")
})
t.Run("reject empty userEmail", func(t *testing.T) {
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: "user123",
UserEmail: "",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.Error(t, err)
assert.Contains(t, err.Error(), "userEmail cannot be empty")
})
t.Run("reject nil eulaVersionID", func(t *testing.T) {
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: "user123",
UserEmail: "user@test.com",
EulaVersionID: uuid.Nil,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.Error(t, err)
assert.Contains(t, err.Error(), "eulaVersionID cannot be nil")
})
t.Run("reject empty ipAddress", func(t *testing.T) {
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: "user123",
UserEmail: "user@test.com",
EulaVersionID: version.ID,
IPAddress: "",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.Error(t, err)
assert.Contains(t, err.Error(), "ipAddress cannot be empty")
})
}
func TestHasUserAgreedToCurrent(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create and activate a version
input := &eula.CreateVersionInput{
Version: "hasagreed-" + uuid.New().String()[:8],
Title: "Has Agreed EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, version.ID, "admin@test.com")
require.NoError(t, err)
t.Run("return false for user who has not agreed", func(t *testing.T) {
hasAgreed, err := svc.HasUserAgreedToCurrent(ctx, "non-existent-user")
require.NoError(t, err)
assert.False(t, hasAgreed)
})
t.Run("return true for user who has agreed", func(t *testing.T) {
userSubject := "agreed-user-" + uuid.New().String()[:8]
// Record agreement
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: "agreed@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
// Check if agreed
hasAgreed, err := svc.HasUserAgreedToCurrent(ctx, userSubject)
require.NoError(t, err)
assert.True(t, hasAgreed)
})
}
func TestGetUserEulaStatus(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create and activate a version
versionStr := "status-" + uuid.New().String()[:8]
input := &eula.CreateVersionInput{
Version: versionStr,
Title: "Status EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, version.ID, "admin@test.com")
require.NoError(t, err)
t.Run("return status for user who has not agreed", func(t *testing.T) {
status, err := svc.GetUserEulaStatus(ctx, "new-user-"+uuid.New().String()[:8])
require.NoError(t, err)
assert.False(t, status.HasAgreed)
assert.Equal(t, versionStr, status.CurrentVersion)
assert.Equal(t, version.ID, status.CurrentVersionID)
assert.Nil(t, status.AgreedAt)
assert.Nil(t, status.AgreedVersion)
})
t.Run("return status for user who has agreed", func(t *testing.T) {
userSubject := "status-user-" + uuid.New().String()[:8]
// Record agreement
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: "status@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
// Get status
status, err := svc.GetUserEulaStatus(ctx, userSubject)
require.NoError(t, err)
assert.True(t, status.HasAgreed)
assert.Equal(t, versionStr, status.CurrentVersion)
assert.NotNil(t, status.AgreedAt)
assert.NotNil(t, status.AgreedVersion)
assert.Equal(t, versionStr, *status.AgreedVersion)
})
}
func TestListAgreements(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create a version for agreements
input := &eula.CreateVersionInput{
Version: "listagreements-" + uuid.New().String()[:8],
Title: "List Agreements EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
// Create multiple agreements
for i := 0; i < 5; i++ {
userSubject := "list-user-" + uuid.New().String()[:8]
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: userSubject + "@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1." + string(rune('1'+i)),
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
}
t.Run("list with pagination", func(t *testing.T) {
result, err := svc.ListAgreements(ctx, &eula.ListAgreementsInput{
Page: 1,
PageSize: 3,
})
require.NoError(t, err)
assert.LessOrEqual(t, len(result.Agreements), 3)
})
t.Run("filter by version ID", func(t *testing.T) {
result, err := svc.ListAgreements(ctx, &eula.ListAgreementsInput{
Page: 1,
PageSize: 20,
VersionID: &version.ID,
})
require.NoError(t, err)
assert.GreaterOrEqual(t, len(result.Agreements), 5)
for _, a := range result.Agreements {
assert.Equal(t, version.ID, a.Eulaversionid)
}
})
t.Run("apply default pagination values", func(t *testing.T) {
result, err := svc.ListAgreements(ctx, &eula.ListAgreementsInput{
Page: 0,
PageSize: 0,
})
require.NoError(t, err)
assert.LessOrEqual(t, len(result.Agreements), 20)
})
}
func TestGetUserAgreementHistory(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
userSubject := "history-user-" + uuid.New().String()[:8]
// Create multiple versions and record agreements for same user
for i := 0; i < 3; i++ {
input := &eula.CreateVersionInput{
Version: "history-v" + string(rune('1'+i)) + "-" + uuid.New().String()[:8],
Title: "History EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: userSubject,
UserEmail: "history@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err = svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
}
t.Run("get all agreements for user", func(t *testing.T) {
history, err := svc.GetUserAgreementHistory(ctx, userSubject)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(history), 3)
})
t.Run("return empty for user with no agreements", func(t *testing.T) {
history, err := svc.GetUserAgreementHistory(ctx, "no-history-user")
require.NoError(t, err)
assert.Len(t, history, 0)
})
}
// TestComplianceHelperFunctions tests the compliance report helper functions
// that don't require Cognito.
func TestComplianceHelperFunctions(t *testing.T) {
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("applyCompliancePaginationDefaults_sets_defaults", func(t *testing.T) {
input := &eula.ComplianceReportInput{
Page: 0,
PageSize: 0,
}
svc.ApplyCompliancePaginationDefaults(input)
assert.Equal(t, int32(1), input.Page)
assert.Equal(t, int32(50), input.PageSize)
})
t.Run("applyCompliancePaginationDefaults_caps_page_size", func(t *testing.T) {
input := &eula.ComplianceReportInput{
Page: 1,
PageSize: 500,
}
svc.ApplyCompliancePaginationDefaults(input)
assert.Equal(t, int32(100), input.PageSize)
})
t.Run("safeIntToInt32_handles_normal_values", func(t *testing.T) {
assert.Equal(t, int32(100), eula.SafeIntToInt32(100))
assert.Equal(t, int32(0), eula.SafeIntToInt32(0))
assert.Equal(t, int32(0), eula.SafeIntToInt32(-5))
})
t.Run("compareComplianceUsers_agreed_first", func(t *testing.T) {
agreedAt := time.Now()
agreed := eula.ComplianceReportUser{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "z@test.com"}
notAgreed := eula.ComplianceReportUser{Agreed: false, CurrentEmail: "a@test.com"}
assert.True(t, eula.CompareComplianceUsers(agreed, notAgreed))
assert.False(t, eula.CompareComplianceUsers(notAgreed, agreed))
})
t.Run("compareComplianceUsers_agreed_sorted_by_date", func(t *testing.T) {
recent := time.Now()
older := time.Now().Add(-24 * time.Hour)
user1 := eula.ComplianceReportUser{Agreed: true, AgreedAt: &recent, CurrentEmail: "a@test.com"}
user2 := eula.ComplianceReportUser{Agreed: true, AgreedAt: &older, CurrentEmail: "b@test.com"}
assert.True(t, eula.CompareComplianceUsers(user1, user2))
assert.False(t, eula.CompareComplianceUsers(user2, user1))
})
t.Run("compareComplianceUsers_notagreed_sorted_by_email", func(t *testing.T) {
user1 := eula.ComplianceReportUser{Agreed: false, CurrentEmail: "a@test.com"}
user2 := eula.ComplianceReportUser{Agreed: false, CurrentEmail: "b@test.com"}
assert.True(t, eula.CompareComplianceUsers(user1, user2))
assert.False(t, eula.CompareComplianceUsers(user2, user1))
})
t.Run("paginateComplianceUsers_calculates_correctly", func(t *testing.T) {
users := make([]eula.ComplianceReportUser, 25)
for i := range users {
users[i] = eula.ComplianceReportUser{CurrentEmail: "user" + string(rune('A'+i)) + "@test.com"}
}
paginated, totalItems, totalPages := svc.PaginateComplianceUsers(users, 1, 10)
assert.Equal(t, 10, len(paginated))
assert.Equal(t, int32(25), totalItems)
assert.Equal(t, int32(3), totalPages)
// Second page
paginated2, _, _ := svc.PaginateComplianceUsers(users, 2, 10)
assert.Equal(t, 10, len(paginated2))
// Third page (partial)
paginated3, _, _ := svc.PaginateComplianceUsers(users, 3, 10)
assert.Equal(t, 5, len(paginated3))
})
t.Run("paginateComplianceUsers_handles_empty", func(t *testing.T) {
users := []eula.ComplianceReportUser{}
paginated, totalItems, totalPages := svc.PaginateComplianceUsers(users, 1, 10)
assert.Equal(t, 0, len(paginated))
assert.Equal(t, int32(0), totalItems)
assert.Equal(t, int32(1), totalPages) // At least 1 page
})
t.Run("filterAndSortComplianceUsers_filters_agreed", func(t *testing.T) {
agreedAt := time.Now()
users := []eula.ComplianceReportUser{
{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "agreed1@test.com"},
{Agreed: false, CurrentEmail: "notagreed1@test.com"},
{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "agreed2@test.com"},
{Agreed: false, CurrentEmail: "notagreed2@test.com"},
}
agreedFilter := true
filtered := svc.FilterAndSortComplianceUsers(users, &agreedFilter)
assert.Equal(t, 2, len(filtered))
for _, u := range filtered {
assert.True(t, u.Agreed)
}
})
t.Run("filterAndSortComplianceUsers_filters_not_agreed", func(t *testing.T) {
agreedAt := time.Now()
users := []eula.ComplianceReportUser{
{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "agreed1@test.com"},
{Agreed: false, CurrentEmail: "notagreed1@test.com"},
{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "agreed2@test.com"},
{Agreed: false, CurrentEmail: "notagreed2@test.com"},
}
notAgreedFilter := false
filtered := svc.FilterAndSortComplianceUsers(users, &notAgreedFilter)
assert.Equal(t, 2, len(filtered))
for _, u := range filtered {
assert.False(t, u.Agreed)
}
})
t.Run("filterAndSortComplianceUsers_nil_filter_returns_all", func(t *testing.T) {
agreedAt := time.Now()
users := []eula.ComplianceReportUser{
{Agreed: true, AgreedAt: &agreedAt, CurrentEmail: "agreed@test.com"},
{Agreed: false, CurrentEmail: "notagreed@test.com"},
}
filtered := svc.FilterAndSortComplianceUsers(users, nil)
assert.Equal(t, 2, len(filtered))
// First user should be the agreed one (sorted)
assert.True(t, filtered[0].Agreed)
})
}
// TestGetComplianceReport tests the compliance report functionality.
// These tests require real Cognito access - opt-in via env var.
func TestGetComplianceReport(t *testing.T) {
// Opt-in: requires ENABLE_COGNITO_INTEGRATION_TESTS=true
if os.Getenv("ENABLE_COGNITO_INTEGRATION_TESTS") != "true" {
t.Skip("Skipping Cognito integration test: set ENABLE_COGNITO_INTEGRATION_TESTS=true to run")
}
userPoolID := os.Getenv("COGNITO_USER_POOL_ID")
awsRegion := os.Getenv("AWS_REGION")
if awsRegion == "" {
awsRegion = os.Getenv("AUTH_REGION")
}
if userPoolID == "" || awsRegion == "" {
t.Skip("Skipping compliance report test: COGNITO_USER_POOL_ID and AWS_REGION/AUTH_REGION must be set")
}
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Set up Cognito client
awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
require.NoError(t, err, "Failed to load AWS config")
cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg)
// Create and activate a version for testing
versionStr := "compliance-" + uuid.New().String()[:8]
input := &eula.CreateVersionInput{
Version: versionStr,
Title: "Compliance Test EULA",
Content: "# Test Content\n\nThis is test content for compliance report testing.",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, version.ID, "admin@test.com")
require.NoError(t, err)
t.Run("returns_report_for_current_version", func(t *testing.T) {
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 50,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Verify version is the current one
assert.Equal(t, version.ID, result.Version.ID)
assert.True(t, result.Version.Iscurrent)
// Verify summary counts are consistent
assert.Equal(t, result.Summary.TotalUsers, result.Summary.AgreedCount+result.Summary.NotAgreedCount)
// Verify pagination info
assert.Equal(t, int32(1), result.Page)
assert.Equal(t, int32(50), result.PageSize)
assert.GreaterOrEqual(t, result.TotalPages, int32(1))
})
t.Run("returns_report_for_specific_version", func(t *testing.T) {
// Create a non-current version
nonCurrentVersion, err := svc.CreateVersion(ctx, &eula.CreateVersionInput{
Version: "specific-" + uuid.New().String()[:8],
Title: "Specific Version EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
})
require.NoError(t, err)
reportInput := &eula.ComplianceReportInput{
VersionID: &nonCurrentVersion.ID,
Page: 1,
PageSize: 50,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Verify version matches the specified one
assert.Equal(t, nonCurrentVersion.ID, result.Version.ID)
})
t.Run("returns_error_for_nonexistent_version", func(t *testing.T) {
nonExistentID := uuid.New()
reportInput := &eula.ComplianceReportInput{
VersionID: &nonExistentID,
Page: 1,
PageSize: 50,
}
_, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrVersionNotFound)
})
t.Run("filters_agreed_only", func(t *testing.T) {
agreed := true
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 50,
Agreed: &agreed,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// All users in the result should have agreed=true
for _, user := range result.Users {
assert.True(t, user.Agreed, "Expected all users to have agreed=true when filtering agreed only")
}
})
t.Run("filters_not_agreed_only", func(t *testing.T) {
agreed := false
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 50,
Agreed: &agreed,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// All users in the result should have agreed=false
for _, user := range result.Users {
assert.False(t, user.Agreed, "Expected all users to have agreed=false when filtering not agreed only")
}
})
t.Run("pagination_works", func(t *testing.T) {
// Request small page size to test pagination
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 2,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Verify pagination calculations
if result.TotalItems > 2 {
assert.LessOrEqual(t, len(result.Users), 2)
assert.Greater(t, result.TotalPages, int32(1))
}
// Request page 2
reportInput.Page = 2
result2, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result2)
assert.Equal(t, int32(2), result2.Page)
})
t.Run("applies_default_pagination", func(t *testing.T) {
reportInput := &eula.ComplianceReportInput{
Page: 0,
PageSize: 0,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Defaults should be applied
assert.Equal(t, int32(1), result.Page)
assert.Equal(t, int32(50), result.PageSize)
})
t.Run("caps_page_size_at_100", func(t *testing.T) {
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 500,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Page size should be capped
assert.Equal(t, int32(100), result.PageSize)
})
t.Run("compliance_percentage_calculation", func(t *testing.T) {
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 50,
}
result, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.NoError(t, err)
require.NotNil(t, result)
// Verify percentage is between 0 and 100
assert.GreaterOrEqual(t, result.Summary.CompliancePercentage, float32(0))
assert.LessOrEqual(t, result.Summary.CompliancePercentage, float32(100))
// Verify percentage calculation if there are users
if result.Summary.TotalUsers > 0 {
expectedPct := float32(result.Summary.AgreedCount) / float32(result.Summary.TotalUsers) * 100
assert.InDelta(t, expectedPct, result.Summary.CompliancePercentage, 0.01)
}
})
}
// TestGetComplianceReportNoCurrentVersion tests error handling when no current version exists.
func TestGetComplianceReportNoCurrentVersion(t *testing.T) {
// Opt-in: requires ENABLE_COGNITO_INTEGRATION_TESTS=true
if os.Getenv("ENABLE_COGNITO_INTEGRATION_TESTS") != "true" {
t.Skip("Skipping Cognito integration test: set ENABLE_COGNITO_INTEGRATION_TESTS=true to run")
}
userPoolID := os.Getenv("COGNITO_USER_POOL_ID")
awsRegion := os.Getenv("AWS_REGION")
if awsRegion == "" {
awsRegion = os.Getenv("AUTH_REGION")
}
if userPoolID == "" || awsRegion == "" {
t.Skip("Skipping compliance report test: COGNITO_USER_POOL_ID and AWS_REGION/AUTH_REGION must be set")
}
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg) // Fresh DB with no EULA versions
svc := eula.New(cfg)
// Set up Cognito client
awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
require.NoError(t, err, "Failed to load AWS config")
cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg)
t.Run("returns_error_when_no_current_version", func(t *testing.T) {
reportInput := &eula.ComplianceReportInput{
Page: 1,
PageSize: 50,
}
_, err := svc.GetComplianceReport(ctx, cognitoClient, userPoolID, reportInput)
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrNoCurrentVersion)
})
}
// TestResolveComplianceVersion tests the ResolveComplianceVersion function.
// This test uses real DB and doesn't require Cognito.
func TestResolveComplianceVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create and activate a version
versionStr := "resolve-" + uuid.New().String()[:8]
input := &eula.CreateVersionInput{
Version: versionStr,
Title: "Resolve Test EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
_, err = svc.ActivateVersion(ctx, version.ID, "admin@test.com")
require.NoError(t, err)
t.Run("resolves_current_version_when_nil", func(t *testing.T) {
resolved, err := svc.ResolveComplianceVersion(ctx, nil)
require.NoError(t, err)
assert.Equal(t, version.ID, resolved.ID)
assert.True(t, resolved.Iscurrent)
})
t.Run("resolves_specific_version_when_provided", func(t *testing.T) {
// Create a non-current version
nonCurrent, err := svc.CreateVersion(ctx, &eula.CreateVersionInput{
Version: "noncurrent-" + uuid.New().String()[:8],
Title: "Non-current EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
})
require.NoError(t, err)
resolved, err := svc.ResolveComplianceVersion(ctx, &nonCurrent.ID)
require.NoError(t, err)
assert.Equal(t, nonCurrent.ID, resolved.ID)
assert.False(t, resolved.Iscurrent)
})
t.Run("returns_error_for_nonexistent_version", func(t *testing.T) {
nonExistentID := uuid.New()
_, err := svc.ResolveComplianceVersion(ctx, &nonExistentID)
require.Error(t, err)
assert.ErrorIs(t, err, eula.ErrVersionNotFound)
})
}
// TestBuildComplianceUserList tests the BuildComplianceUserList function.
// This test uses mock data and doesn't require Cognito or DB.
func TestBuildComplianceUserList(t *testing.T) {
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("builds_user_list_with_agreements", func(t *testing.T) {
versionID := uuid.New()
agreedAt := time.Now()
cognitoUsers := []usermanagement.CognitoUserResponse{
{SubjectID: "user1", Email: "user1@test.com", Enabled: true},
{SubjectID: "user2", Email: "user2@test.com", Enabled: true},
{SubjectID: "user3", Email: "user3@test.com", Enabled: true},
}
agreements := []*repository.Eulaagreement{
{
ID: uuid.New(),
Cognitosubjectid: "user1",
Useremail: "user1@test.com",
Eulaversionid: versionID,
Agreedat: pgtype.Timestamptz{Time: agreedAt, Valid: true},
Agreedfromip: "192.168.1.1",
},
}
users, summary := svc.BuildComplianceUserList(cognitoUsers, agreements)
// Verify user count
assert.Equal(t, 3, len(users))
// Verify summary
assert.Equal(t, int32(3), summary.TotalUsers)
assert.Equal(t, int32(1), summary.AgreedCount)
assert.Equal(t, int32(2), summary.NotAgreedCount)
assert.InDelta(t, 33.33, summary.CompliancePercentage, 0.1)
// Verify user1 is marked as agreed
var user1Found bool
for _, u := range users {
if u.CognitoSubjectID == "user1" {
user1Found = true
assert.True(t, u.Agreed)
assert.NotNil(t, u.AgreedAt)
assert.Equal(t, "192.168.1.1", *u.AgreedFromIP)
}
}
assert.True(t, user1Found, "user1 should be in the list")
})
t.Run("handles_empty_users", func(t *testing.T) {
users, summary := svc.BuildComplianceUserList(nil, nil)
assert.Equal(t, 0, len(users))
assert.Equal(t, int32(0), summary.TotalUsers)
assert.Equal(t, int32(0), summary.AgreedCount)
assert.Equal(t, int32(0), summary.NotAgreedCount)
assert.Equal(t, float32(0), summary.CompliancePercentage)
})
t.Run("handles_all_agreed", func(t *testing.T) {
versionID := uuid.New()
agreedAt := time.Now()
cognitoUsers := []usermanagement.CognitoUserResponse{
{SubjectID: "user1", Email: "user1@test.com", Enabled: true},
{SubjectID: "user2", Email: "user2@test.com", Enabled: true},
}
agreements := []*repository.Eulaagreement{
{
ID: uuid.New(),
Cognitosubjectid: "user1",
Useremail: "user1@test.com",
Eulaversionid: versionID,
Agreedat: pgtype.Timestamptz{Time: agreedAt, Valid: true},
Agreedfromip: "192.168.1.1",
},
{
ID: uuid.New(),
Cognitosubjectid: "user2",
Useremail: "user2@test.com",
Eulaversionid: versionID,
Agreedat: pgtype.Timestamptz{Time: agreedAt, Valid: true},
Agreedfromip: "192.168.1.2",
},
}
users, summary := svc.BuildComplianceUserList(cognitoUsers, agreements)
assert.Equal(t, 2, len(users))
assert.Equal(t, int32(2), summary.TotalUsers)
assert.Equal(t, int32(2), summary.AgreedCount)
assert.Equal(t, int32(0), summary.NotAgreedCount)
assert.Equal(t, float32(100), summary.CompliancePercentage)
})
}
// TestBuildComplianceUser tests the BuildComplianceUser function.
// This test uses mock data and doesn't require Cognito.
func TestBuildComplianceUser(t *testing.T) {
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
t.Run("builds_user_with_agreement", func(t *testing.T) {
agreedAt := time.Now()
cognitoUser := usermanagement.CognitoUserResponse{
SubjectID: "user1",
Email: "user1@test.com",
Enabled: true,
}
agreementMap := map[string]*repository.Eulaagreement{
"user1": {
ID: uuid.New(),
Cognitosubjectid: "user1",
Useremail: "user1-old@test.com",
Eulaversionid: uuid.New(),
Agreedat: pgtype.Timestamptz{Time: agreedAt, Valid: true},
Agreedfromip: "10.0.0.1",
},
}
user := svc.BuildComplianceUser(cognitoUser, agreementMap)
assert.Equal(t, "user1", user.CognitoSubjectID)
assert.Equal(t, "user1@test.com", user.CurrentEmail)
assert.True(t, user.Agreed)
assert.NotNil(t, user.Email)
assert.Equal(t, "user1-old@test.com", *user.Email)
assert.NotNil(t, user.AgreedAt)
assert.Equal(t, "10.0.0.1", *user.AgreedFromIP)
})
t.Run("builds_user_without_agreement", func(t *testing.T) {
cognitoUser := usermanagement.CognitoUserResponse{
SubjectID: "user2",
Email: "user2@test.com",
Enabled: true,
}
agreementMap := make(map[string]*repository.Eulaagreement)
user := svc.BuildComplianceUser(cognitoUser, agreementMap)
assert.Equal(t, "user2", user.CognitoSubjectID)
assert.Equal(t, "user2@test.com", user.CurrentEmail)
assert.False(t, user.Agreed)
assert.Nil(t, user.Email)
assert.Nil(t, user.AgreedAt)
assert.Nil(t, user.AgreedFromIP)
})
t.Run("handles_missing_agreedat", func(t *testing.T) {
cognitoUser := usermanagement.CognitoUserResponse{
SubjectID: "user3",
Email: "user3@test.com",
Enabled: true,
}
agreementMap := map[string]*repository.Eulaagreement{
"user3": {
ID: uuid.New(),
Cognitosubjectid: "user3",
Useremail: "user3@test.com",
Eulaversionid: uuid.New(),
Agreedat: pgtype.Timestamptz{Valid: false}, // Not set
Agreedfromip: "10.0.0.3",
},
}
user := svc.BuildComplianceUser(cognitoUser, agreementMap)
assert.True(t, user.Agreed)
assert.Nil(t, user.AgreedAt)
assert.Equal(t, "10.0.0.3", *user.AgreedFromIP)
})
}
// TestFetchAllAgreementsForVersion tests the FetchAllAgreementsForVersion function.
// This test uses real DB and doesn't require Cognito.
func TestFetchAllAgreementsForVersion(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
svc := eula.New(cfg)
// Create a version
versionStr := "fetch-agreements-" + uuid.New().String()[:8]
input := &eula.CreateVersionInput{
Version: versionStr,
Title: "Fetch Agreements EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
}
version, err := svc.CreateVersion(ctx, input)
require.NoError(t, err)
t.Run("returns_empty_for_version_with_no_agreements", func(t *testing.T) {
agreements, err := svc.FetchAllAgreementsForVersion(ctx, version.ID)
require.NoError(t, err)
assert.Equal(t, 0, len(agreements))
})
t.Run("returns_agreements_for_version", func(t *testing.T) {
// Record some agreements
for i := 0; i < 5; i++ {
agreementInput := &eula.RecordAgreementInput{
CognitoSubjectID: "fetch-user-" + uuid.New().String()[:8],
UserEmail: "user@test.com",
EulaVersionID: version.ID,
IPAddress: "192.168.1.1",
}
_, err := svc.RecordAgreement(ctx, agreementInput)
require.NoError(t, err)
}
agreements, err := svc.FetchAllAgreementsForVersion(ctx, version.ID)
require.NoError(t, err)
assert.Equal(t, 5, len(agreements))
})
t.Run("returns_only_agreements_for_specific_version", func(t *testing.T) {
// Create another version
otherVersion, err := svc.CreateVersion(ctx, &eula.CreateVersionInput{
Version: "other-" + uuid.New().String()[:8],
Title: "Other EULA",
Content: "Content",
EffectiveDate: ptrTime(time.Now()),
CreatedBy: "admin@test.com",
})
require.NoError(t, err)
// Record agreement for the other version
_, err = svc.RecordAgreement(ctx, &eula.RecordAgreementInput{
CognitoSubjectID: "other-user-" + uuid.New().String()[:8],
UserEmail: "other@test.com",
EulaVersionID: otherVersion.ID,
IPAddress: "192.168.1.1",
})
require.NoError(t, err)
// Fetch agreements for original version - should not include other version's agreement
agreements, err := svc.FetchAllAgreementsForVersion(ctx, version.ID)
require.NoError(t, err)
// All agreements should be for our version
for _, agreement := range agreements {
assert.Equal(t, version.ID, agreement.Eulaversionid)
}
})
}