package store import ( "context" "path/filepath" "testing" "time" "github.com/oklog/ulid/v2" ) func newSetupTokenTestStore(t *testing.T) (*Store, string, string) { t.Helper() st, err := Open(context.Background(), filepath.Join(t.TempDir(), "rm.db")) if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { _ = st.Close() }) uid := ulid.Make().String() creator := ulid.Make().String() now := time.Now().UTC() if err := st.CreateUser(context.Background(), User{ ID: creator, Username: "creator", PasswordHash: "x", Role: RoleAdmin, CreatedAt: now, }); err != nil { t.Fatalf("create creator: %v", err) } if err := st.CreateUser(context.Background(), User{ ID: uid, Username: "target", PasswordHash: "", Role: RoleOperator, CreatedAt: now, MustChangePassword: true, }); err != nil { t.Fatalf("create target: %v", err) } return st, uid, creator } func TestSetupTokenSetAndLookup(t *testing.T) { t.Parallel() st, uid, creator := newSetupTokenTestStore(t) ctx := context.Background() now := time.Now().UTC() if err := st.SetSetupToken(ctx, SetupToken{ UserID: uid, TokenHash: "abc123", ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator, }); err != nil { t.Fatalf("set: %v", err) } got, err := st.LookupSetupToken(ctx, "abc123") if err != nil { t.Fatalf("lookup: %v", err) } if got.UserID != uid { t.Errorf("user_id: got %q want %q", got.UserID, uid) } } func TestSetupTokenReplaces(t *testing.T) { t.Parallel() st, uid, creator := newSetupTokenTestStore(t) ctx := context.Background() now := time.Now().UTC() _ = st.SetSetupToken(ctx, SetupToken{ UserID: uid, TokenHash: "old", ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator, }) _ = st.SetSetupToken(ctx, SetupToken{ UserID: uid, TokenHash: "new", ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator, }) if _, err := st.LookupSetupToken(ctx, "old"); err == nil { t.Error("old token should be gone") } if _, err := st.LookupSetupToken(ctx, "new"); err != nil { t.Errorf("new token should resolve: %v", err) } } func TestSetupTokenDelete(t *testing.T) { t.Parallel() st, uid, creator := newSetupTokenTestStore(t) ctx := context.Background() now := time.Now().UTC() _ = st.SetSetupToken(ctx, SetupToken{ UserID: uid, TokenHash: "tk", ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator, }) if err := st.DeleteSetupToken(ctx, uid); err != nil { t.Fatalf("delete: %v", err) } if _, err := st.LookupSetupToken(ctx, "tk"); err == nil { t.Error("deleted token should not resolve") } } func TestSetupTokenCleanupExpired(t *testing.T) { t.Parallel() st, uid, creator := newSetupTokenTestStore(t) ctx := context.Background() now := time.Now().UTC() _ = st.SetSetupToken(ctx, SetupToken{ UserID: uid, TokenHash: "stale", ExpiresAt: now.Add(-time.Hour), CreatedAt: now.Add(-2 * time.Hour), CreatedBy: &creator, }) n, err := st.CleanupExpiredSetupTokens(ctx, now) if err != nil { t.Fatalf("cleanup: %v", err) } if n != 1 { t.Errorf("cleanup count: got %d want 1", n) } if _, err := st.LookupSetupToken(ctx, "stale"); err == nil { t.Error("stale token should be gone") } }