Go app for capturing and archiving HSA-eligible receipts: OIDC/PKCE auth against Authelia, SQLite storage with dual-write (filesystem + DB blob), mobile-first upload, and DB export. Adds AI receipt classification: a config.json catalog of people and categories (seeded into the DB on startup), a prompt builder that derives name-order/initial variants from the data (with same-surname ambiguity handling), and an Anthropic tool-use client behind POST /classify. Tests run against a mock endpoint; a live integration test is env-gated to the cheapest model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
1.8 KiB
Go
77 lines
1.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testKey() [32]byte {
|
|
var k [32]byte
|
|
copy(k[:], "test-key-32-bytes-exactly-padded")
|
|
return k
|
|
}
|
|
|
|
func TestSessionRoundTrip(t *testing.T) {
|
|
key := testKey()
|
|
sess := Session{
|
|
Subject: "jeanmi@example.com",
|
|
Groups: []string{"hsa-users", "admins"},
|
|
IssuedAt: time.Now().UTC().Truncate(time.Second),
|
|
}
|
|
|
|
encoded, err := EncodeSession(sess, key)
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
|
|
got, err := DecodeSession(encoded, key)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
|
|
if got.Subject != sess.Subject {
|
|
t.Errorf("subject: got %q want %q", got.Subject, sess.Subject)
|
|
}
|
|
if len(got.Groups) != len(sess.Groups) || got.Groups[0] != sess.Groups[0] {
|
|
t.Errorf("groups: got %v want %v", got.Groups, sess.Groups)
|
|
}
|
|
if !got.IssuedAt.Equal(sess.IssuedAt) {
|
|
t.Errorf("issuedAt: got %v want %v", got.IssuedAt, sess.IssuedAt)
|
|
}
|
|
}
|
|
|
|
func TestSessionTamperedRejected(t *testing.T) {
|
|
key := testKey()
|
|
sess := Session{Subject: "someone", Groups: []string{"hsa-users"}, IssuedAt: time.Now()}
|
|
|
|
encoded, err := EncodeSession(sess, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Flip a byte near the end of the ciphertext.
|
|
b := []byte(encoded)
|
|
b[len(b)-4] ^= 0xFF
|
|
tampered := string(b)
|
|
|
|
if _, err := DecodeSession(tampered, key); err == nil {
|
|
t.Error("expected error for tampered cookie, got nil")
|
|
}
|
|
}
|
|
|
|
func TestSessionWrongKeyRejected(t *testing.T) {
|
|
key := testKey()
|
|
sess := Session{Subject: "someone", Groups: []string{"hsa-users"}, IssuedAt: time.Now()}
|
|
|
|
encoded, err := EncodeSession(sess, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var otherKey [32]byte
|
|
copy(otherKey[:], "different-key-32-bytes-padded!!!")
|
|
|
|
if _, err := DecodeSession(encoded, otherKey); err == nil {
|
|
t.Error("expected error for wrong key, got nil")
|
|
}
|
|
}
|