单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。 Co-authored-by: Cursor <cursoragent@cursor.com>
146 lines
4.1 KiB
Go
146 lines
4.1 KiB
Go
// 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
|
||
}
|