package store import ( "path/filepath" "strings" "testing" ) func newTestTokens(t *testing.T) (*Tokens, string) { t.Helper() path := filepath.Join(t.TempDir(), "tokens.json") tk, err := NewTokens(path) if err != nil { t.Fatalf("NewTokens: %v", err) } return tk, path } func TestTokensCreateAndAuthenticate(t *testing.T) { tk, _ := newTestTokens(t) tok, plain, err := tk.Create("user-1", "ci token") if err != nil { t.Fatalf("Create: %v", err) } if tok.Hash != "" { t.Fatal("Create must return a redacted token") } if !strings.HasPrefix(plain, "dmv_") { t.Fatalf("plaintext = %q, want dmv_ prefix", plain) } if tok.Hint != plain[len(plain)-4:] { t.Fatalf("hint = %q, want last 4 chars of %q", tok.Hint, plain) } got, err := tk.Authenticate(plain) if err != nil { t.Fatalf("Authenticate: %v", err) } if got.UserID != "user-1" { t.Fatalf("UserID = %q, want user-1", got.UserID) } if got.Hash != "" { t.Fatal("Authenticate must return a redacted token") } if _, err := tk.Authenticate("dmv_deadbeef"); err != ErrTokenNotFound { t.Fatalf("Authenticate with bogus token = %v, want ErrTokenNotFound", err) } } func TestTokensAuthenticateRecordsLastUsed(t *testing.T) { tk, _ := newTestTokens(t) tok, plain, err := tk.Create("user-1", "ci token") if err != nil { t.Fatalf("Create: %v", err) } if tok.LastUsedAt != nil { t.Fatal("a fresh token should have no last-used time") } got, err := tk.Authenticate(plain) if err != nil { t.Fatalf("Authenticate: %v", err) } if got.LastUsedAt == nil { t.Fatal("Authenticate should record LastUsedAt") } } func TestTokensListScopedToOwner(t *testing.T) { tk, _ := newTestTokens(t) if _, _, err := tk.Create("user-1", "a"); err != nil { t.Fatalf("Create: %v", err) } if _, _, err := tk.Create("user-2", "b"); err != nil { t.Fatalf("Create: %v", err) } list := tk.List("user-1") if len(list) != 1 || list[0].Name != "a" { t.Fatalf("List(user-1) = %+v, want just token a", list) } } func TestTokensRevokeOwnershipChecked(t *testing.T) { tk, _ := newTestTokens(t) tok, plain, err := tk.Create("user-1", "a") if err != nil { t.Fatalf("Create: %v", err) } if err := tk.Revoke("user-2", tok.ID); err != ErrTokenNotFound { t.Fatalf("Revoke by non-owner = %v, want ErrTokenNotFound", err) } if err := tk.Revoke("user-1", tok.ID); err != nil { t.Fatalf("Revoke by owner: %v", err) } if _, err := tk.Authenticate(plain); err != ErrTokenNotFound { t.Fatalf("Authenticate after revoke = %v, want ErrTokenNotFound", err) } } func TestTokensRevokeAllForUser(t *testing.T) { tk, _ := newTestTokens(t) if _, _, err := tk.Create("user-1", "a"); err != nil { t.Fatalf("Create: %v", err) } if _, _, err := tk.Create("user-1", "b"); err != nil { t.Fatalf("Create: %v", err) } if _, _, err := tk.Create("user-2", "c"); err != nil { t.Fatalf("Create: %v", err) } if err := tk.RevokeAllForUser("user-1"); err != nil { t.Fatalf("RevokeAllForUser: %v", err) } if len(tk.List("user-1")) != 0 { t.Fatal("user-1 should have no tokens left") } if len(tk.List("user-2")) != 1 { t.Fatal("user-2's token should be untouched") } } func TestTokensPersistAcrossReload(t *testing.T) { tk, path := newTestTokens(t) _, plain, err := tk.Create("user-1", "a") if err != nil { t.Fatalf("Create: %v", err) } reopened, err := NewTokens(path) if err != nil { t.Fatalf("NewTokens: %v", err) } if _, err := reopened.Authenticate(plain); err != nil { t.Fatalf("Authenticate after reload: %v", err) } }