add token type with permission checks and composable matchers

This commit is contained in:
juancwu 2026-04-29 01:58:46 +00:00
commit 8b5fcc87c3
4 changed files with 499 additions and 0 deletions

202
token_test.go Normal file
View file

@ -0,0 +1,202 @@
package ficha
import (
"encoding/json"
"testing"
"time"
)
func makeToken(perms ...string) *Token {
return &Token{
id: "tok_test",
issuedAt: time.Unix(1_700_000_000, 0),
expiresAt: time.Unix(1_700_003_600, 0),
permissions: perms,
}
}
func TestTokenAccessors(t *testing.T) {
tok := makeToken("read", "write")
if tok.ID() != "tok_test" {
t.Errorf("ID: got %q", tok.ID())
}
if got := tok.IssuedAt().Unix(); got != 1_700_000_000 {
t.Errorf("IssuedAt: got %d", got)
}
if got := tok.ExpiresAt().Unix(); got != 1_700_003_600 {
t.Errorf("ExpiresAt: got %d", got)
}
perms := tok.Permissions()
if len(perms) != 2 || perms[0] != "read" || perms[1] != "write" {
t.Errorf("Permissions: got %v", perms)
}
}
func TestTokenPermissionsIsCopy(t *testing.T) {
tok := makeToken("read", "write")
perms := tok.Permissions()
perms[0] = "tampered"
if tok.Has("tampered") {
t.Error("mutating returned slice affected token state")
}
}
func TestTokenHas(t *testing.T) {
tok := makeToken("read", "write", "admin")
cases := map[string]bool{
"read": true,
"write": true,
"admin": true,
"delete": false,
"": false,
}
for perm, want := range cases {
if got := tok.Has(perm); got != want {
t.Errorf("Has(%q): got %v, want %v", perm, got, want)
}
}
}
func TestTokenHasAll(t *testing.T) {
tok := makeToken("a", "b", "c")
tests := []struct {
name string
perms []string
want bool
}{
{"all present", []string{"a", "b"}, true},
{"all three", []string{"a", "b", "c"}, true},
{"one missing", []string{"a", "z"}, false},
{"none present", []string{"x", "y"}, false},
{"empty input is vacuously true", []string{}, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tok.HasAll(tc.perms...); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}
func TestTokenHasAny(t *testing.T) {
tok := makeToken("a", "b")
tests := []struct {
name string
perms []string
want bool
}{
{"one matches", []string{"a", "z"}, true},
{"all match", []string{"a", "b"}, true},
{"none match", []string{"x", "y"}, false},
{"empty input is false", []string{}, false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tok.HasAny(tc.perms...); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}
func TestTokenHasNone(t *testing.T) {
tok := makeToken("a", "b")
tests := []struct {
name string
perms []string
want bool
}{
{"none of them present", []string{"x", "y"}, true},
{"one is present", []string{"a", "y"}, false},
{"empty input is vacuously true", []string{}, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tok.HasNone(tc.perms...); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}
func TestTokenUnmarshalData(t *testing.T) {
type meta struct {
UserID string `json:"user_id"`
Tier int `json:"tier"`
}
raw, _ := json.Marshal(meta{UserID: "u_42", Tier: 3})
tok := &Token{data: raw}
var got meta
if err := tok.UnmarshalData(&got); err != nil {
t.Fatalf("UnmarshalData: %v", err)
}
if got.UserID != "u_42" || got.Tier != 3 {
t.Errorf("got %+v", got)
}
}
func TestTokenUnmarshalDataEmpty(t *testing.T) {
tok := &Token{}
var got map[string]any
if err := tok.UnmarshalData(&got); err != nil {
t.Errorf("expected nil error on empty data, got %v", err)
}
}
func TestNewToken(t *testing.T) {
p := payload{
ID: "tok_x",
Iat: 1_700_000_000,
Exp: 1_700_003_600,
Permissions: []string{"read"},
Data: json.RawMessage(`{"x":1}`),
}
tok := newToken(p)
if tok.ID() != "tok_x" {
t.Errorf("ID: got %q", tok.ID())
}
if !tok.Has("read") {
t.Error("expected Has(read)")
}
if tok.IssuedAt().Unix() != 1_700_000_000 {
t.Errorf("IssuedAt: %v", tok.IssuedAt())
}
}
func TestTokenRequiresAll(t *testing.T) {
tok := makeToken("a", "b", "c")
tests := []struct {
name string
perms []string
want bool
}{
{"all present", []string{"a", "b"}, true},
{"all three", []string{"a", "b", "c"}, true},
{"one missing", []string{"a", "z"}, false},
{"none present", []string{"x", "y"}, false},
{"empty input fails closed", []string{}, false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tok.RequiresAll(tc.perms...); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}