package data_test import ( "context" "fmt" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nixcn-cms/data" "nixcn-cms/testutil" ) func seedEventAndUser(t *testing.T, ctx context.Context) (eventId, userId uuid.UUID) { t.Helper() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) return ev.EventId, u.UserId } func seedAttendance(t *testing.T, ctx context.Context, eventId, userId uuid.UUID) *data.Attendance { t.Helper() a := data.NewAttendance( data.WithEventId(eventId), data.WithUserId(userId), data.WithKycId(uuid.Nil), data.WithRole("attendee"), data.WithState("success"), ) _, err := a.Create(ctx) require.NoError(t, err) return a } func TestAttendanceCreate(t *testing.T) { testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) a := seedAttendance(t, ctx, eventId, userId) assert.NotEqual(t, uuid.Nil, a.AttendanceId) } func TestAttendanceGetAttendance(t *testing.T) { testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) seedAttendance(t, ctx, eventId, userId) got, err := new(data.Attendance).GetAttendance(ctx, userId, eventId) require.NoError(t, err) require.NotNil(t, got) assert.Equal(t, eventId, got.EventId) assert.Equal(t, userId, got.UserId) } func TestAttendanceGetAttendanceNotFound(t *testing.T) { testutil.Setup(t) ctx := context.Background() got, err := new(data.Attendance).GetAttendance(ctx, uuid.New(), uuid.New()) require.NoError(t, err) assert.Nil(t, got) } func TestAttendancePatch(t *testing.T) { testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) a := seedAttendance(t, ctx, eventId, userId) now := time.Now().Truncate(time.Second) updated, err := new(data.Attendance).PatchByAttendanceId(ctx, a.AttendanceId, data.WithState("suspended"), data.WithCheckinAt(now), ) require.NoError(t, err) assert.Equal(t, "suspended", updated.State) } func TestAttendanceCountByEventID(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 3; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) seedAttendance(t, ctx, ev.EventId, u.UserId) } count, err := new(data.Attendance).CountUsersByEventID(ctx, ev.EventId) require.NoError(t, err) assert.Equal(t, int64(3), count) } func TestAttendanceGetJoinedEventIDs(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev1 := makeEvent(owner) require.NoError(t, ev1.Create(ctx)) ev2 := makeEvent(owner) require.NoError(t, ev2.Create(ctx)) u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) seedAttendance(t, ctx, ev1.EventId, u.UserId) joined, err := new(data.Attendance).GetJoinedEventIDs(ctx, u.UserId, []uuid.UUID{ev1.EventId, ev2.EventId}) require.NoError(t, err) assert.True(t, joined[ev1.EventId]) assert.False(t, joined[ev2.EventId]) } func TestAttendanceGenAndVerifyCheckinCode(t *testing.T) { testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) a := seedAttendance(t, ctx, eventId, userId) code, err := a.GenCheckinCode(ctx, a.EventId) require.NoError(t, err) require.NotNil(t, code) assert.Len(t, *code, 6) require.NoError(t, new(data.Attendance).VerifyCheckinCode(ctx, *code)) got, err := new(data.Attendance).GetAttendance(ctx, userId, eventId) require.NoError(t, err) assert.False(t, got.CheckinAt.IsZero()) } func TestAttendanceVerifyCheckinCodeInvalid(t *testing.T) { testutil.Setup(t) ctx := context.Background() err := new(data.Attendance).VerifyCheckinCode(ctx, "000000") require.Error(t, err) } func TestAttendanceGetByAttendanceId(t *testing.T) { testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) a := seedAttendance(t, ctx, eventId, userId) got, err := new(data.Attendance).GetAttendanceByAttendanceId(ctx, a.AttendanceId) require.NoError(t, err) require.NotNil(t, got) assert.Equal(t, a.AttendanceId, got.AttendanceId) assert.Equal(t, eventId, got.EventId) assert.Equal(t, userId, got.UserId) } func TestAttendanceGetByAttendanceIdNotFound(t *testing.T) { testutil.Setup(t) ctx := context.Background() got, err := new(data.Attendance).GetAttendanceByAttendanceId(ctx, uuid.New()) require.NoError(t, err) assert.Nil(t, got) } func TestAttendanceGetUsersByEventID(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 3; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) seedAttendance(t, ctx, ev.EventId, u.UserId) } users, err := new(data.Attendance).GetUsersByEventID(ctx, ev.EventId) require.NoError(t, err) require.NotNil(t, users) assert.Len(t, *users, 3) } func TestAttendanceGetEventsByUserID(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev1 := makeEvent(owner) require.NoError(t, ev1.Create(ctx)) ev2 := makeEvent(owner) require.NoError(t, ev2.Create(ctx)) u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) seedAttendance(t, ctx, ev1.EventId, u.UserId) seedAttendance(t, ctx, ev2.EventId, u.UserId) events, err := new(data.Attendance).GetEventsByUserID(ctx, u.UserId) require.NoError(t, err) require.NotNil(t, events) assert.Len(t, *events, 2) } func TestAttendanceGetAttendanceListByEventId(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 4; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) seedAttendance(t, ctx, ev.EventId, u.UserId) } list, err := new(data.Attendance).GetAttendanceListByEventId(ctx, ev.EventId) require.NoError(t, err) require.NotNil(t, list) assert.Len(t, *list, 4) } func TestAttendanceGetCheckedInEventIDs(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev1 := makeEvent(owner) require.NoError(t, ev1.Create(ctx)) ev2 := makeEvent(owner) require.NoError(t, ev2.Create(ctx)) u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) a1 := seedAttendance(t, ctx, ev1.EventId, u.UserId) _, err := new(data.Attendance).PatchByAttendanceId(ctx, a1.AttendanceId, data.WithCheckinAt(time.Now())) require.NoError(t, err) seedAttendance(t, ctx, ev2.EventId, u.UserId) checkedIn, err := new(data.Attendance).GetCheckedInEventIDs(ctx, u.UserId, []uuid.UUID{ev1.EventId, ev2.EventId}) require.NoError(t, err) assert.True(t, checkedIn[ev1.EventId]) assert.False(t, checkedIn[ev2.EventId]) } func TestAttendanceCountCheckedInUsersByEventID(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 3; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) a := seedAttendance(t, ctx, ev.EventId, u.UserId) if i < 2 { _, err := new(data.Attendance).PatchByAttendanceId(ctx, a.AttendanceId, data.WithCheckinAt(time.Now())) require.NoError(t, err) } } count, err := new(data.Attendance).CountCheckedInUsersByEventID(ctx, ev.EventId) require.NoError(t, err) assert.Equal(t, int64(2), count) } func TestAttendanceCountWithKycByEventID(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 3; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) kycId := uuid.Nil if i < 2 { kycId = uuid.New() } a := data.NewAttendance( data.WithEventId(ev.EventId), data.WithUserId(u.UserId), data.WithKycId(kycId), data.WithRole("attendee"), data.WithState("success"), ) _, err := a.Create(ctx) require.NoError(t, err) } count, err := new(data.Attendance).CountWithKycByEventID(ctx, ev.EventId) require.NoError(t, err) assert.Equal(t, int64(2), count) } func TestAttendanceGetAttendanceListFiltered(t *testing.T) { testutil.Setup(t) ctx := context.Background() owner := uuid.New() ev := makeEvent(owner) require.NoError(t, ev.Create(ctx)) for i := 0; i < 5; i++ { u := data.NewUser( data.WithEmail(uuid.New().String()+"@test.com"), data.WithUsername(uuid.New().String()), data.WithNickname(fmt.Sprintf("FilterUser%d", i)), data.WithPermissionLevel(10), ) require.NoError(t, u.Create(ctx)) kycId := uuid.Nil if i < 3 { kycId = uuid.New() } a := data.NewAttendance( data.WithEventId(ev.EventId), data.WithUserId(u.UserId), data.WithKycId(kycId), data.WithRole("attendee"), data.WithState("success"), ) _, err := a.Create(ctx) require.NoError(t, err) } t.Run("no_filters_returns_all", func(t *testing.T) { list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, Limit: 20, }) require.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, *list, 5) }) t.Run("with_kyc_filter", func(t *testing.T) { list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, KycStatus: "with_kyc", Limit: 20, }) require.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, *list, 3) }) t.Run("without_kyc_filter", func(t *testing.T) { list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, KycStatus: "without_kyc", Limit: 20, }) require.NoError(t, err) assert.Equal(t, int64(2), total) assert.Len(t, *list, 2) }) t.Run("name_filter", func(t *testing.T) { list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, Name: "FilterUser1", Limit: 20, }) require.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, *list, 1) }) t.Run("pagination", func(t *testing.T) { list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, Limit: 2, Offset: 0, }) require.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, *list, 2) }) t.Run("sort_by_id_asc", func(t *testing.T) { list, _, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{ EventId: ev.EventId, SortBy: "id", SortOrder: "asc", Limit: 20, }) require.NoError(t, err) require.NotNil(t, list) items := *list for i := 1; i < len(items); i++ { assert.GreaterOrEqual(t, items[i].Id, items[i-1].Id) } }) } func TestAttendanceCheckinCodeExpiry(t *testing.T) { mr := testutil.Setup(t) ctx := context.Background() eventId, userId := seedEventAndUser(t, ctx) a := seedAttendance(t, ctx, eventId, userId) code, err := a.GenCheckinCode(ctx, a.EventId) require.NoError(t, err) require.NotNil(t, code) mr.FastForward(10 * time.Minute) err = new(data.Attendance).VerifyCheckinCode(ctx, *code) require.Error(t, err, "expired code should not be valid") } func TestAttendanceGetCheckedInEventIDsEmpty(t *testing.T) { testutil.Setup(t) ctx := context.Background() result, err := new(data.Attendance).GetCheckedInEventIDs(ctx, uuid.New(), []uuid.UUID{}) require.NoError(t, err) assert.Empty(t, result) }