Files
knowledge-governance/internal/objectgateway/unknown_integration_test.go
T
姚章浩 5eef13b615 feat(object-gateway): 统一对象写网关与不可变治理日志
方案A:所有对象写入经 loopback 网关,持久计划/grant/intent/逐版本 receipt 与
attempt→grant delivery 落在治理 PG;scope 由 provider+endpoint+bucket 决定,
同一物理桶只允许一个写域。版本 append 不等于 CAS,未锚定计划在首笔业务写前拒绝。

本提交只含网关与其适配层接线、协议文档;生产独占、凭据切换与桶设置未改动。
2026-11-01 11:35:05 +08:00

70 lines
2.2 KiB
Go

package objectgateway_test
import (
"context"
"errors"
"testing"
"github.com/jackc/pgx/v5/pgxpool"
g "knowledge-governance/internal/objectgateway"
)
type stoppedBeforeSend struct {
g.Backend
calls int
}
func (b *stoppedBeforeSend) Put(context.Context, string, []byte, string) (g.Version, error) {
b.calls++
return g.Version{}, errors.New("injected uncertain send with no visible version")
}
func TestGatewayAbsenceNeverSettlesUnknown(t *testing.T) {
ctx, pool, real := fixture(t)
backend := &stoppedBeforeSend{Backend: real}
grant := g.Grant{Epoch: 1, Owner: g.Digest([]byte(t.Name())), Plan: g.Digest([]byte("plan"))}
gateway := &g.Gateway{Pool: pool, Scope: real.Bucket, Backend: backend, Isolation: verifiedFixture{}, MaxBytes: 1 << 20}
if e := gateway.InstallPlan(ctx, planFor(grant, request(t.Name(), grant))); e != nil {
t.Fatal(e)
}
if e := gateway.Advance(ctx, 0, grant); e != nil {
t.Fatal(e)
}
req := request(t.Name(), grant)
body := []byte("gateway fixture " + t.Name())
if _, e := gateway.Execute(ctx, req, body); !errors.Is(e, g.ErrHeld) {
t.Fatal(e)
}
restarted := *gateway
for i := 0; i < 2; i++ {
if _, e := restarted.Execute(ctx, req, body); !errors.Is(e, g.ErrHeld) {
t.Fatal("absent version treated as terminal", e)
}
}
if backend.calls != 1 {
t.Fatal("PUT replayed", backend.calls)
}
next := grant
next.Epoch++
if e := restarted.Advance(ctx, 1, next); !errors.Is(e, g.ErrHeld) {
t.Fatal("unknown operation allowed epoch handover", e)
}
var n int
if pool.QueryRow(ctx, "SELECT count(*) FROM kg_object_gateway_receipts WHERE scope=$1", gateway.Scope).Scan(&n) != nil || n != 0 {
t.Fatal("absence wrote false terminal receipt")
}
}
func TestGatewayReadOnlyPersistenceBlocksSend(t *testing.T) {
ctx, pool, backend := fixture(t)
cfg := pool.Config()
cfg.ConnConfig.RuntimeParams["default_transaction_read_only"] = "on"
other, e := pgxpool.NewWithConfig(ctx, cfg)
if e != nil {
t.Fatal(e)
}
defer other.Close()
gateway := &g.Gateway{Pool: other, Scope: backend.Bucket, Backend: backend, Isolation: verifiedFixture{}, MaxBytes: 1 << 20}
if e = gateway.Ready(ctx); !errors.Is(e, g.ErrPersistence) {
t.Fatal("read-only persistence accepted", e)
}
}