rastrillo / idear Public

Clone
git clone https://amadan.net/rastrillo/idear

Plain git — no account needed to clone.

Download

Download this file

1package idear
2
3import (
4 "testing"
5 "time"
6)
7
8// TestMember_Active_Nil checks that a nil Member is never active — the
9// contract Active() documents so callers can pass a lookup's zero value
10// straight in without a separate nil check.
11func TestMember_Active_Nil(t *testing.T) {
12 var m *Member
13 if m.Active() {
14 t.Fatalf("nil.Active() = true, want false")
15 }
16}
17
18// TestMember_Active_Deactivated checks that a member with a non-nil
19// DeactivatedAt is not active, regardless of how long ago.
20func TestMember_Active_Deactivated(t *testing.T) {
21 at := time.Unix(0, 0)
22 m := &Member{ID: 1, Role: RoleMember, DeactivatedAt: &at}
23 if m.Active() {
24 t.Fatalf("Active() = true for a deactivated member, want false")
25 }
26}
27
28// TestMember_Active_True checks the remaining case: a real member with
29// no DeactivatedAt is active.
30func TestMember_Active_True(t *testing.T) {
31 m := &Member{ID: 1, Role: RoleMember}
32 if !m.Active() {
33 t.Fatalf("Active() = false for a live member, want true")
34 }
35}
36
37// The four Pending tests below each flip exactly one field away from
38// the "would otherwise be pending" baseline, so each one independently
39// proves its own reason for refusal. A single case combining all three
40// reasons would pass even if two of the three checks were missing from
41// the implementation.
42
43// TestInvitation_Pending_Accepted checks that an accepted invitation
44// is never pending, even though it is neither revoked nor expired.
45func TestInvitation_Pending_Accepted(t *testing.T) {
46 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
47 acceptedAt := now.Add(-time.Hour)
48 inv := &Invitation{
49 ExpiresAt: now.Add(24 * time.Hour),
50 AcceptedAt: &acceptedAt,
51 }
52 if inv.Pending(now) {
53 t.Fatalf("Pending(now) = true for an accepted invitation, want false")
54 }
55}
56
57// TestInvitation_Pending_Revoked checks that a revoked invitation is
58// never pending, even though it is neither accepted nor expired.
59func TestInvitation_Pending_Revoked(t *testing.T) {
60 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
61 revokedAt := now.Add(-time.Hour)
62 inv := &Invitation{
63 ExpiresAt: now.Add(24 * time.Hour),
64 RevokedAt: &revokedAt,
65 }
66 if inv.Pending(now) {
67 t.Fatalf("Pending(now) = true for a revoked invitation, want false")
68 }
69}
70
71// TestInvitation_Pending_Expired checks that an expired invitation is
72// never pending, even though it is neither accepted nor revoked.
73func TestInvitation_Pending_Expired(t *testing.T) {
74 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
75 inv := &Invitation{
76 ExpiresAt: now.Add(-time.Second),
77 }
78 if inv.Pending(now) {
79 t.Fatalf("Pending(now) = true for an expired invitation, want false")
80 }
81}
82
83// TestInvitation_Pending_ExpiresAtNow pins the exact boundary:
84// ExpiresAt == now is not pending. Pending uses ExpiresAt.After(now),
85// so an invitation expiring at exactly now must read as already
86// expired — matching the CAS SQL Task 3 writes, "expires_at > ?",
87// which also excludes the boundary. Left unpinned, a future edit to
88// use !now.After(ExpiresAt) (or similar) would flip this one instant
89// without any other test noticing.
90func TestInvitation_Pending_ExpiresAtNow(t *testing.T) {
91 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
92 inv := &Invitation{
93 ExpiresAt: now,
94 }
95 if inv.Pending(now) {
96 t.Fatalf("Pending(now) = true when ExpiresAt == now exactly, want false")
97 }
98}
99
100// TestInvitation_Pending_True checks the remaining case: unaccepted,
101// unrevoked, unexpired is pending.
102func TestInvitation_Pending_True(t *testing.T) {
103 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
104 inv := &Invitation{
105 ExpiresAt: now.Add(24 * time.Hour),
106 }
107 if !inv.Pending(now) {
108 t.Fatalf("Pending(now) = false for a live invitation, want true")
109 }
110}
111