package auth import ( "context" "testing" "time" "github.com/stretchr/testify/require" ) func TestDecodeWalletSignatureRejectsMalformedValues(t *testing.T) { _, err := decodeWalletSignature("deadbeef") require.ErrorContains(t, err, "signature must start with 0x") _, err = decodeWalletSignature("0x1234") require.ErrorContains(t, err, "invalid signature length") } func TestValidateJWTReturnsClaimsWhenDBUnavailable(t *testing.T) { secret := []byte("test-secret") auth := NewWalletAuth(nil, secret) token, _, err := auth.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 4) require.NoError(t, err) address, track, err := auth.ValidateJWT(token) require.NoError(t, err) require.Equal(t, "0x4A666F96fC8764181194447A7dFdb7d471b301C8", address) require.Equal(t, 4, track) } func TestTokenTTLForTrack4IsShort(t *testing.T) { // Track 4 (operator) must have a TTL <= 1h — that is the headline // tightening promised by completion criterion 3 (JWT hygiene). ttl := tokenTTLFor(4) require.LessOrEqual(t, ttl, time.Hour, "track 4 TTL must be <= 1h") require.Greater(t, ttl, time.Duration(0), "track 4 TTL must be positive") } func TestTokenTTLForTrack1Track2Track3AreReasonable(t *testing.T) { // Non-operator tracks are allowed longer sessions, but still bounded // at 12h so a stale laptop tab doesn't carry a week-old token. for _, track := range []int{1, 2, 3} { ttl := tokenTTLFor(track) require.Greater(t, ttl, time.Duration(0), "track %d TTL must be > 0", track) require.LessOrEqual(t, ttl, 12*time.Hour, "track %d TTL must be <= 12h", track) } } func TestGeneratedJWTCarriesJTIClaim(t *testing.T) { // Revocation keys on jti. A token issued without one is unrevokable // and must not be produced. a := NewWalletAuth(nil, []byte("test-secret")) token, _, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 2) require.NoError(t, err) jti, err := a.jtiFromToken(token) require.NoError(t, err) require.NotEmpty(t, jti, "generated JWT must carry a jti claim") require.Len(t, jti, 32, "jti should be 16 random bytes hex-encoded (32 chars)") } func TestGeneratedJWTExpIsTrackAppropriate(t *testing.T) { a := NewWalletAuth(nil, []byte("test-secret")) for _, track := range []int{1, 2, 3, 4} { _, expiresAt, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", track) require.NoError(t, err) want := tokenTTLFor(track) // allow a couple-second slack for test execution actual := time.Until(expiresAt) require.InDelta(t, want.Seconds(), actual.Seconds(), 5.0, "track %d exp should be ~%s from now, got %s", track, want, actual) } } func TestRevokeJWTWithoutDBReturnsError(t *testing.T) { // With w.db == nil, revocation has nowhere to write — the call must // fail loudly so callers don't silently assume a token was revoked. a := NewWalletAuth(nil, []byte("test-secret")) token, _, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 4) require.NoError(t, err) err = a.RevokeJWT(context.Background(), token, "test") require.Error(t, err) require.Contains(t, err.Error(), "no database") }