Files
knowledge-governance/internal/httpapi/library_authorization_test.go
T
姚章浩 7b8cc0384b
build / build (push) Successful in 1m19s
feat(library): 生产发布授权与同步/恢复编排,worker 接线
为什么:同步入口此前只会拒绝;授权、预备份、四类写入、验证与失败恢复都没有代码。
本轮把编排与授权实现出来,真实生产执行仍按授权闸口关闭。

- internal/libraryrelease:预备份 → 数据库/资源/索引/Wiki → 验证 → 失败恢复;
  授权过期/目标不符/快照不符/参数变化在动生产前拒绝;恢复失败单独上报。
- 迁移 000067 library_release_authorizations(目标+快照+参数摘要+有效期,可撤销)。
- POST/GET /api/v1/library/authorizations 与 /{id}/revoke,仅管理员签发,5–1440 分钟。
- worker library.sync 接编排:先闸口再授权,阶段落库,只有编排成功才标成功。
- 生产目标适配器未配置时显式失败(不返回零值状态),预备份存储同。
- overview 返回授权状态,页面"生产保护与恢复"显示签发人与有效期。
- 验收矩阵区分"已实现·隔离测试通过"与"真实执行未授权"。
2026-10-28 15:11:27 +08:00

85 lines
3.2 KiB
Go

package httpapi
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"knowledge-governance/internal/governance"
)
func authorizationRequest(body string, role string, username string) *http.Request {
request := httptest.NewRequest(http.MethodPost, "/api/v1/library/authorizations", strings.NewReader(body))
request.Header.Set("Content-Type", "application/json")
ctx := context.WithValue(request.Context(), sessionKey{}, sessionIdentity{Username: username, Role: role})
return request.WithContext(ctx)
}
func TestLibraryAuthorizationRequiresAdmin(t *testing.T) {
store := &fakeLibraryStore{}
recorder := httptest.NewRecorder()
libraryAuthorizationCreate(recorder, authorizationRequest(`{"scope":"internal","snapshot_id":"snap-1","expires_in_minutes":30}`, roleEngineer, "eng"), Config{Store: store})
if recorder.Code != http.StatusForbidden {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
if store.grant != nil {
t.Fatal("non-admin must not create an authorization")
}
}
func TestLibraryAuthorizationBindsScopeSnapshotAndDigest(t *testing.T) {
store := &fakeLibraryStore{}
recorder := httptest.NewRecorder()
libraryAuthorizationCreate(recorder, authorizationRequest(`{"scope":"internal","snapshot_id":"snap-1","expires_in_minutes":30}`, roleAdmin, "admin"), Config{Store: store})
if recorder.Code != http.StatusCreated {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
want, err := librarySyncParamsDigest("internal", "snap-1")
if err != nil {
t.Fatal(err)
}
if store.grant.ParamsDigest != want || store.grant.SnapshotID != "snap-1" || store.grant.Scope != "internal" {
t.Fatalf("grant not bound to request params: %+v", store.grant)
}
// 同一个授权不能用于另一个快照:摘要不同即查不到。
if _, found, _ := store.ActiveLibraryAuthorization(context.Background(), "internal", "snap-2", want, time.Now()); found {
t.Fatal("grant must not authorize another snapshot")
}
if _, found, _ := store.ActiveLibraryAuthorization(context.Background(), "internal", "snap-1", "other-digest", time.Now()); found {
t.Fatal("grant must not authorize different params")
}
}
func TestLibraryAuthorizationRejectsUnboundedValidity(t *testing.T) {
for _, body := range []string{
`{"scope":"internal","snapshot_id":"snap-1","expires_in_minutes":0}`,
`{"scope":"internal","snapshot_id":"snap-1","expires_in_minutes":100000}`,
} {
store := &fakeLibraryStore{}
recorder := httptest.NewRecorder()
libraryAuthorizationCreate(recorder, authorizationRequest(body, roleAdmin, "admin"), Config{Store: store})
if recorder.Code != http.StatusBadRequest {
t.Fatalf("body=%s status=%d", body, recorder.Code)
}
}
}
func TestLibraryAuthorizationExpiryIsEnforced(t *testing.T) {
now := time.Now()
item := governance.LibraryReleaseAuthorization{ID: "a", Scope: "internal", SnapshotID: "snap-1",
ParamsDigest: "d", ExpiresAt: now.Add(-time.Minute)}
if item.ValidAt(now) {
t.Fatal("expired authorization must be invalid")
}
revoked := item
revoked.ExpiresAt = now.Add(time.Hour)
revokedAt := now
revoked.RevokedAt = &revokedAt
if revoked.ValidAt(now) {
t.Fatal("revoked authorization must be invalid")
}
}