onesvm-browser-server/server/internal/auth/auth_test.go
chii eb972dfa93 feat: 落地 browser-server 控制面并打通 mgr1 海外订阅
单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:05:12 +08:00

146 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// auth_test.go:签发/校验(常数时间比对路径)/过期/吊销/配额预扣。
package auth
import (
"path/filepath"
"testing"
"time"
_ "modernc.org/sqlite" // sqlite driver(auth 测试库)
"onesvm.com/onesvm/browser-server/internal/store"
)
func newTestVerifier(t *testing.T) *Verifier {
t.Helper()
db, err := store.Open(filepath.Join(t.TempDir(), "t.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
if err := db.Migrate(); err != nil {
t.Fatal(err)
}
return NewVerifier(db, "test-seed-salt-not-a-secret-in-tests")
}
// TestIssueAndCheck 签发后校验通过;篡改 key 拒绝。
func TestIssueAndCheck(t *testing.T) {
v := newTestVerifier(t)
cid, _ := v.db.CreateConsumer("c", "")
plaintext, keyID, err := v.Issue(cid, "k1", nil, 60, 100, 1000, 2, nil)
if err != nil {
t.Fatal(err)
}
if len(plaintext) < len(KeyPrefix)+8 {
t.Fatalf("明文 key 形态不符: %s", plaintext)
}
k, aerr := v.Check(plaintext)
if aerr != nil {
t.Fatalf("合法 key 应通过: %v", aerr)
}
if k.ID != keyID {
t.Fatalf("key id 不符: %d != %d", k.ID, keyID)
}
// 篡改一位
bad := plaintext[:len(plaintext)-1] + "A"
if _, aerr := v.Check(bad); aerr == nil {
t.Fatal("篡改 key 应拒绝")
}
// 格式错误
if _, aerr := v.Check("Bearer xyz"); aerr == nil {
t.Fatal("非法前缀应拒绝")
}
}
// TestRevoke 吊销即时生效。
func TestRevoke(t *testing.T) {
v := newTestVerifier(t)
cid, _ := v.db.CreateConsumer("c2", "")
plaintext, keyID, _ := v.Issue(cid, "k2", nil, 60, 100, 1000, 2, nil)
if _, aerr := v.Check(plaintext); aerr != nil {
t.Fatalf("吊销前应通过: %v", aerr)
}
if err := v.db.SetKeyStatus(keyID, "disabled"); err != nil {
t.Fatal(err)
}
if _, aerr := v.Check(plaintext); aerr == nil || aerr.Code != "unauthorized" {
t.Fatalf("吊销后应拒绝: %v", aerr)
}
}
// TestExpiry 过期 key 拒绝。
func TestExpiry(t *testing.T) {
v := newTestVerifier(t)
cid, _ := v.db.CreateConsumer("c3", "")
past := time.Now().Add(-time.Hour)
plaintext, _, err := v.Issue(cid, "k3", nil, 60, 100, 1000, 2, &past)
if err != nil {
t.Fatal(err)
}
if _, aerr := v.Check(plaintext); aerr == nil || aerr.Message != "key 已过期" {
t.Fatalf("过期 key 应拒绝: %v", aerr)
}
}
// TestConstantTimePath 假比对覆盖(查无此 key 时也走常数时间路径,不 panic)。
func TestConstantTimePath(t *testing.T) {
v := newTestVerifier(t)
if _, aerr := v.Check("bs_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); aerr == nil {
t.Fatal("未知 key 应拒绝")
}
}
// TestScopes scope 判定。
func TestScopes(t *testing.T) {
v := newTestVerifier(t)
cid, _ := v.db.CreateConsumer("c4", "")
plaintext, _, _ := v.Issue(cid, "k4", []string{"search"}, 60, 100, 1000, 2, nil)
k, aerr := v.Check(plaintext)
if aerr != nil {
t.Fatal(aerr)
}
if !HasScope(k, "search") || HasScope(k, "screenshot") {
t.Fatalf("scope 判定不符: %v", k.Scopes)
}
}
// TestQuotaFlow 预扣→402→结算/释放。
func TestQuotaFlow(t *testing.T) {
v := newTestVerifier(t)
cid, _ := v.db.CreateConsumer("c5", "")
plaintext, keyID, _ := v.Issue(cid, "k5", nil, 60, 2, 100, 2, nil)
k, _ := v.Check(plaintext)
now := time.Now()
if _, aerr := v.ReserveDaily(keyID, k.DailyQuota, now); aerr != nil {
t.Fatalf("第 1 次预扣应通过: %v", aerr)
}
if _, aerr := v.ReserveDaily(keyID, k.DailyQuota, now); aerr != nil {
t.Fatalf("第 2 次预扣应通过: %v", aerr)
}
if _, aerr := v.ReserveDaily(keyID, k.DailyQuota, now); aerr == nil || aerr.Code != "quota" {
t.Fatalf("超限应 402: %v", aerr)
}
// 失败释放后可再扣
if err := v.Release(keyID, now); err != nil {
t.Fatal(err)
}
if _, aerr := v.ReserveDaily(keyID, k.DailyQuota, now); aerr != nil {
t.Fatalf("释放后应可扣: %v", aerr)
}
if err := v.Settle(keyID, now); err != nil {
t.Fatal(err)
}
}
// TestFailClosed 空盐必须 panic(fail-closed 纪律)。
func TestFailClosed(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("空盐应 panic(fail-closed)")
}
}()
db, _ := store.Open(filepath.Join(t.TempDir(), "x.db"))
defer db.Close()
_ = db.Migrate()
NewVerifier(db, "") // 应 panic
}