~bigbes/sr-ht-spec

ref: 394285f4cb998359c0a127454de4d32284a3ef22 sr-ht-spec/cmd/specsrht/token_test.go -rw-r--r-- 2.7 KiB
394285f4 — Eugene Blikh chore(beads): file spec-rsb and spec-ovo, the two admin commands 13 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import (
	"strings"
	"testing"
	"time"

	"sourcecraft.dev/bigbes/sr-ht-spec/db"
)

func TestParseTokenIDRejectsWhatIsNotAnID(t *testing.T) {
	for _, in := range []string{"", "0", "-1", "3.0", "abc", " 3", "3 "} {
		if _, err := parseTokenID(in); err == nil {
			t.Errorf("parseTokenID(%q) was accepted", in)
		}
	}
	id, err := parseTokenID("42")
	if err != nil {
		t.Fatalf("parseTokenID(\"42\"): %v", err)
	}
	if id != 42 {
		t.Errorf("parseTokenID(\"42\") = %d want 42", id)
	}
}

// TestParseTokenIDPointsAtTheListing keeps the failure actionable: an operator
// who mistyped an id needs to be told where the ids come from, not just that
// this one was wrong.
func TestParseTokenIDPointsAtTheListing(t *testing.T) {
	_, err := parseTokenID("nope")
	if err == nil {
		t.Fatal("parseTokenID accepted a non-numeric id")
	}
	if !strings.Contains(err.Error(), "specsrht token list") {
		t.Errorf("the error does not say how to find the ids:\n%v", err)
	}
}

func TestFormatTokenRow(t *testing.T) {
	created := time.Date(2026, 8, 5, 9, 30, 0, 0, time.UTC)
	revoked := created.Add(24 * time.Hour)

	active := formatTokenRow(&db.AgentToken{ID: 1, Name: "claude", Created: created})
	if want := "1\tclaude\t2026-08-05T09:30:00Z\tactive"; active != want {
		t.Errorf("active row = %q want %q", active, want)
	}

	dead := formatTokenRow(&db.AgentToken{ID: 2, Name: "old", Created: created, Revoked: &revoked})
	if want := "2\told\t2026-08-05T09:30:00Z\trevoked 2026-08-06T09:30:00Z"; dead != want {
		t.Errorf("revoked row = %q want %q", dead, want)
	}
}

// TestFormatTokenRowKeepsTheHashOut guards the one thing this listing must not
// leak into an operator's terminal or scrollback: the stored hash is not a
// credential and printing it invites treating it as one.
func TestFormatTokenRowKeepsTheHashOut(t *testing.T) {
	row := formatTokenRow(&db.AgentToken{
		ID:      3,
		Name:    "agent",
		Hash:    db.HashToken("s3cret"),
		Created: time.Date(2026, 8, 5, 9, 30, 0, 0, time.UTC),
	})
	if strings.Contains(row, "[") || strings.ContainsAny(row, "%") {
		t.Errorf("the row appears to render the hash bytes: %q", row)
	}
	if strings.Contains(row, string(db.HashToken("s3cret"))) {
		t.Errorf("the row carries the stored hash: %q", row)
	}
}

// TestRunTokenRejectsBadInvocationsBeforeTheDatabase guards the ordering that
// makes this command usable on a workstation: a usage error must be reported
// without a config file or a Postgres connection, which is what an operator
// typing it blind has.
func TestRunTokenRejectsBadInvocationsBeforeTheDatabase(t *testing.T) {
	err := runToken(nil)
	if err == nil {
		t.Fatal("runToken accepted an empty argument list")
	}
	if err.Error() != tokenUsage {
		t.Errorf("empty invocation did not print the usage line:\n%v", err)
	}
}