package auth import ( "context" "testing" "time" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" ) func TestSignParseRevoke(t *testing.T) { mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) j := NewJWT("test-secret", time.Hour, rdb) ctx := context.Background() token, err := j.Sign("u1") if err != nil { t.Fatal(err) } uid, jti, exp, err := j.Parse(ctx, token) if err != nil || uid != "u1" || jti == "" { t.Fatalf("parse failed: uid=%s jti=%s err=%v", uid, jti, err) } if exp.IsZero() || time.Until(exp) <= 0 { t.Fatalf("expiresAt should be in the future, got %v", exp) } // 拉黑后解析失败 if err := j.Revoke(ctx, jti, time.Now().Add(time.Hour)); err != nil { t.Fatal(err) } if _, _, _, err := j.Parse(ctx, token); err == nil { t.Fatal("expect parse failure after revoke") } // 篡改签名失败 if _, _, _, err := j.Parse(ctx, token+"x"); err == nil { t.Fatal("expect parse failure on tampered token") } } // TestParseFailClosed Redis 不可用时黑名单检查应 fail-closed:拒绝放行(17A)。 func TestParseFailClosed(t *testing.T) { mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) j := NewJWT("test-secret", time.Hour, rdb) ctx := context.Background() token, err := j.Sign("u1") if err != nil { t.Fatal(err) } // 正常情况下放行 if _, _, _, err := j.Parse(ctx, token); err != nil { t.Fatalf("parse should pass while redis up: %v", err) } // Redis 故障:Exists 返回 error → 必须拒绝(不得 fail-open)。 mr.Close() if _, _, _, err := j.Parse(ctx, token); err == nil { t.Fatal("expect parse failure when redis is unavailable (fail-closed)") } }