Merge authoritative main into the task branch
Why: include the current integration baseline before verification. Evidence: fetched origin/main=63e70d8b91ccb1691ce9b78a373e4e8bc9da24a2. Impact: integrate upstream changes without rewriting task history. Refs: task-sync # Conflicts: # modules/backend
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
Submodule modules/backend updated: 06ef17a70a...c8c49a6511
@@ -40,16 +40,16 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
aliceOwner = "mi_alice"
|
aliceOwner = "mi_alice"
|
||||||
alicePass0 = "mi-alice-pass-0"
|
|
||||||
alicePass = "mi-alice-pass-1"
|
alicePass = "mi-alice-pass-1"
|
||||||
httpWait = 15 * time.Second
|
httpWait = 15 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type instance struct {
|
type instance struct {
|
||||||
name string
|
name string
|
||||||
base string
|
base string
|
||||||
cmd *exec.Cmd
|
dataDir string // serve 的 cwd:本地实例身份(manifest)所在,账户 CLI 须以此身份运行
|
||||||
log *lockedBuffer
|
cmd *exec.Cmd
|
||||||
|
log *lockedBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
type lockedBuffer struct {
|
type lockedBuffer struct {
|
||||||
@@ -312,6 +312,20 @@ func (e *env) startPair() {
|
|||||||
e.t.Helper()
|
e.t.Helper()
|
||||||
e.a = e.startInstance(e.t, "miA", "127.0.0.1:"+freePort(e.t))
|
e.a = e.startInstance(e.t, "miA", "127.0.0.1:"+freePort(e.t))
|
||||||
e.b = e.startInstance(e.t, "miB", "127.0.0.1:"+freePort(e.t))
|
e.b = e.startInstance(e.t, "miB", "127.0.0.1:"+freePort(e.t))
|
||||||
|
// 库内服务身份是显式引导(svc init 同款),且为部署级单行:双进程共享
|
||||||
|
// 同一身份,只对 A ensure 一次。账户 CLI 的审计与核验都依赖它。
|
||||||
|
e.identityEnsure(e.t, e.a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// identityEnsure 建立/对齐库内服务身份行(agent_system_identity)。
|
||||||
|
func (e *env) identityEnsure(t *testing.T, inst *instance) {
|
||||||
|
t.Helper()
|
||||||
|
cmd := exec.Command(e.bin, "identity", "ensure", "--instance-id", inst.name, "--data", inst.dataDir, "--dbref", "pg")
|
||||||
|
cmd.Dir = inst.dataDir
|
||||||
|
cmd.Env = append(os.Environ(), "AGENT_SYSTEM_DSN="+e.dbDSN)
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("identity ensure %s: %v\n%s", inst.name, err, out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) startInstance(t *testing.T, name, addr string) *instance {
|
func (e *env) startInstance(t *testing.T, name, addr string) *instance {
|
||||||
@@ -332,11 +346,12 @@ func (e *env) startInstance(t *testing.T, name, addr string) *instance {
|
|||||||
// 否则测不到带类型空 *S3 回落本地目录的路径。
|
// 否则测不到带类型空 *S3 回落本地目录的路径。
|
||||||
cmd.Env = withoutBlobstoreS3(os.Environ())
|
cmd.Env = withoutBlobstoreS3(os.Environ())
|
||||||
// 独立 cwd:serve 会向 ./data 写 skills/SQLite 等,不能落在测试目录。
|
// 独立 cwd:serve 会向 ./data 写 skills/SQLite 等,不能落在测试目录。
|
||||||
cmd.Dir = t.TempDir()
|
dataDir := t.TempDir()
|
||||||
|
cmd.Dir = dataDir
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
t.Fatalf("start %s: %v", name, err)
|
t.Fatalf("start %s: %v", name, err)
|
||||||
}
|
}
|
||||||
inst := &instance{name: name, base: "http://" + addr, cmd: cmd, log: log}
|
inst := &instance{name: name, base: "http://" + addr, dataDir: dataDir, cmd: cmd, log: log}
|
||||||
t.Cleanup(func() { e.stopInstance(inst) })
|
t.Cleanup(func() { e.stopInstance(inst) })
|
||||||
e.waitHealthy(t, inst)
|
e.waitHealthy(t, inst)
|
||||||
return inst
|
return inst
|
||||||
@@ -435,29 +450,35 @@ func (e *env) killNine(t *testing.T, inst *instance) {
|
|||||||
|
|
||||||
// loginAlice 引导 alice 账号并发放 API key(CLI 直连测试库;key 存共享 PG,
|
// loginAlice 引导 alice 账号并发放 API key(CLI 直连测试库;key 存共享 PG,
|
||||||
// 对两实例同时有效——程序化验收的首选凭证,SSE 亦可用 ?key= 兜底)。
|
// 对两实例同时有效——程序化验收的首选凭证,SSE 亦可用 ?key= 兜底)。
|
||||||
// 建号后 must_change=1:API Key 在改密完成前不能走业务接口(login_keys 门禁),
|
// U3 后本地无密码创建:user create 只签发待激活账号的一次性激活 Key(凭据
|
||||||
// 故先经会话完成 alicePass0→alicePass,再发 Key。
|
// 独占落 JSON),redeem 会话进入 grant-setup 改密设 alicePass,完成后再发 Key。
|
||||||
func (e *env) loginAlice(t *testing.T) *userClient {
|
func (e *env) loginAlice(t *testing.T) *userClient {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
e.cli(t, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--password", alicePass0)
|
// 空库先引导首个管理员(本地 pending 创建要求库内已有用户),再建 alice。
|
||||||
|
// 两步都签发一次性凭据,独占落临时 JSON 后即弃。
|
||||||
|
rootCred := filepath.Join(t.TempDir(), "root-cred.json")
|
||||||
|
e.cliIn(t, e.a.dataDir, "user", "bootstrap-admin", "--dsn", e.dbDSN, "--owner", "mi-root", "--out-file", rootCred)
|
||||||
|
activation := filepath.Join(t.TempDir(), "activation.json")
|
||||||
|
e.cliIn(t, e.a.dataDir, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--out", activation)
|
||||||
|
activateKey := activationSecret(t, activation)
|
||||||
jar, err := cookiejar.New(nil)
|
jar, err := cookiejar.New(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sess := &http.Client{Timeout: httpWait, Jar: jar}
|
sess := &http.Client{Timeout: httpWait, Jar: jar}
|
||||||
// e.uc 尚未赋值 → baseFor 走 A;会话 cookie 落 jar 后改密清 must_change。
|
// e.uc 尚未赋值 → baseFor 走 A;redeem 会话(grant-setup)直接设 alicePass。
|
||||||
e.do(t, sess, "POST", "/api/auth/login", map[string]any{
|
e.do(t, sess, "POST", "/api/auth/login-key/redeem", map[string]any{
|
||||||
"owner": aliceOwner, "password": alicePass0,
|
"key": activateKey,
|
||||||
}, nil)
|
}, nil)
|
||||||
e.do(t, sess, "POST", "/api/auth/password", map[string]any{
|
e.do(t, sess, "POST", "/api/auth/password", map[string]any{
|
||||||
"current": alicePass0, "next": alicePass,
|
"next": alicePass,
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
key := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
|
apiKey := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
|
||||||
uc := &userClient{
|
uc := &userClient{
|
||||||
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
|
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
|
||||||
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
|
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
|
||||||
streamB: &http.Client{Transport: bearerRT{key: key}}, // SSE 无整体超时;chan 背压控制
|
streamB: &http.Client{Transport: bearerRT{key: apiKey}}, // SSE 无整体超时;chan 背压控制
|
||||||
}
|
}
|
||||||
// runtime.mock 自系统受管设置体制(backend #188/#190)起由 --mock 播种进
|
// runtime.mock 自系统受管设置体制(backend #188/#190)起由 --mock 播种进
|
||||||
// 系统层,经 Effective 链对所有租户生效(selectConversationRunner 按
|
// 系统层,经 Effective 链对所有租户生效(selectConversationRunner 按
|
||||||
@@ -474,6 +495,22 @@ func (b bearerRT) RoundTrip(r *http.Request) (*http.Response, error) {
|
|||||||
return http.DefaultTransport.RoundTrip(r2)
|
return http.DefaultTransport.RoundTrip(r2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// activationSecret 读 CLI 凭据独占文件里的激活 Key(JSON {"kind","owner","secret"})。
|
||||||
|
func activationSecret(t *testing.T, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil || v.Secret == "" {
|
||||||
|
t.Fatalf("activation credential unreadable: %v", err)
|
||||||
|
}
|
||||||
|
return v.Secret
|
||||||
|
}
|
||||||
|
|
||||||
// cliOut 运行 CLI 并取 stdout 首行(apikey create 的明文仅此一次输出)。
|
// cliOut 运行 CLI 并取 stdout 首行(apikey create 的明文仅此一次输出)。
|
||||||
func (e *env) cliOut(t *testing.T, args ...string) string {
|
func (e *env) cliOut(t *testing.T, args ...string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -492,9 +529,16 @@ func (e *env) cliOut(t *testing.T, args ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) cli(t *testing.T, args ...string) {
|
func (e *env) cli(t *testing.T, args ...string) {
|
||||||
|
t.Helper()
|
||||||
|
e.cliIn(t, t.TempDir(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cliIn 以指定 cwd 运行 CLI:账户命令经 assertLocalAccountOwner 核验本地实例
|
||||||
|
// 身份,必须从持有该身份的 dataDir 运行。
|
||||||
|
func (e *env) cliIn(t *testing.T, dir string, args ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cmd := exec.Command(e.bin, args...)
|
cmd := exec.Command(e.bin, args...)
|
||||||
cmd.Dir = t.TempDir()
|
cmd.Dir = dir
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cli %v…: %v\n%s", args[:2], err, out)
|
t.Fatalf("cli %v…: %v\n%s", args[:2], err, out)
|
||||||
|
|||||||
Reference in New Issue
Block a user