// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package tokens import ( "errors" "os" "path/filepath" "strings" "testing" ) func TestCreateAndAuthenticate(t *testing.T) { path := filepath.Join(t.TempDir(), "tokens.toml") s := New(path) record, raw, err := s.Create("ci", nil) if err != nil { t.Fatalf("Create: %v", err) } if record == nil || raw == "" { t.Fatal("Create failed") } if !strings.HasPrefix(raw, TokenPrefix) { t.Fatalf("raw = %q", raw) } if record.TokenHash == raw { t.Fatal("raw token persisted") } found := s.Authenticate(raw) if found == nil || found.Name != "ci" { t.Fatalf("Authenticate = %v", found) } if !found.HasScope("write") { t.Fatal("unrestricted token should grant every scope") } if s.Authenticate("vol_wrong") != nil { t.Fatal("wrong token authenticated") } if s.Authenticate("not-our-prefix") != nil { t.Fatal("foreign prefix authenticated") } info, err := os.Stat(path) if err != nil { t.Fatalf("stat: %v", err) } if info.Mode().Perm() != 0o600 { t.Fatalf("mode = %v", info.Mode().Perm()) } rawFile, err := os.ReadFile(path) if err != nil { t.Fatalf("read: %v", err) } if strings.Contains(string(rawFile), raw) { t.Fatal("raw token leaked into the file") } } func TestCreateScopes(t *testing.T) { s := New(filepath.Join(t.TempDir(), "tokens.toml")) record, _, err := s.Create("scoped", []string{"write", "bogus", "delete"}) if err != nil { t.Fatalf("Create: %v", err) } if record == nil { t.Fatal("Create failed") } if len(record.Scopes) != 2 || record.Scopes[0] != "write" || record.Scopes[1] != "delete" { t.Fatalf("scopes = %v", record.Scopes) } if record.HasScope("read") { t.Fatal("read scope granted") } if !record.HasScope("write") { t.Fatal("write scope missing") } // An explicit list that names no valid scope must be refused, not // turned into an unrestricted token. if _, _, err := s.Create("invalid-only", []string{"bogus"}); !errors.Is(err, ErrNoValidScope) { t.Fatalf("err = %v, want ErrNoValidScope", err) } } func TestCreateRejectsDuplicatesAndEmpty(t *testing.T) { s := New(filepath.Join(t.TempDir(), "tokens.toml")) if record, _, err := s.Create("dup", nil); err != nil || record == nil { t.Fatalf("first Create: %v, %v", record, err) } if record, _, err := s.Create("dup", nil); err == nil || record != nil { t.Fatal("duplicate name accepted") } if record, _, err := s.Create(" ", nil); err == nil || record != nil { t.Fatal("empty name accepted") } } func TestPersistedAcrossReopen(t *testing.T) { path := filepath.Join(t.TempDir(), "tokens.toml") s := New(path) _, raw, err := s.Create("ci", []string{"write"}) if err != nil { t.Fatalf("Create: %v", err) } reopened := New(path) if len(reopened.All()) != 1 { t.Fatalf("tokens = %v", reopened.All()) } if reopened.Authenticate(raw) == nil { t.Fatal("token lost across reopen") } } func TestTouchRefreshesOncePerDay(t *testing.T) { s := New(filepath.Join(t.TempDir(), "tokens.toml")) record, _, err := s.Create("ci", nil) if err != nil { t.Fatalf("Create: %v", err) } s.Touch("ci") after := s.All()[0] if after.LastUsed == "" { t.Fatal("last_used not set") } s.Touch("ci") again := s.All()[0] if again.LastUsed != after.LastUsed { t.Fatal("last_used updated twice in one day") } s.Touch("missing") if record.Created == "" { t.Fatal("created missing") } } func TestRevoke(t *testing.T) { s := New(filepath.Join(t.TempDir(), "tokens.toml")) s.Create("one", nil) if !s.Revoke("one") { t.Fatal("Revoke failed") } if s.Revoke("one") { t.Fatal("Revoke succeeded twice") } if len(s.All()) != 0 { t.Fatalf("tokens = %v", s.All()) } } func TestUnreadableFileYieldsNoTokens(t *testing.T) { path := filepath.Join(t.TempDir(), "tokens.toml") if err := os.WriteFile(path, []byte("broken = = ="), 0o600); err != nil { t.Fatalf("write: %v", err) } if got := New(path).All(); got != nil { t.Fatalf("tokens = %v, want none", got) } } func TestMissingFileYieldsNoTokens(t *testing.T) { if got := New(filepath.Join(t.TempDir(), "nope.toml")).All(); got != nil { t.Fatalf("tokens = %v, want none", got) } }