feat(v3): close key governance with subject-scoped selector and pause/resume on real host
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled

* ensureSubjectHasAccess now uses real SubjectID, not fixed 'portal-user'
* CreateUserKey/ResetUserKey metadata (masked_preview, key_fingerprint) based on actual returned key
* PauseManagedSubscriptionAccess/ResumeManagedSubscriptionAccess update host user allowed_groups
* Remote43 hot-updated with singleton CRM (secondary instance killed to avoid SQLITE_BUSY)
* Fresh JWT issued for remote43 host adapter
* Real E2E: create=201, chat-before=200, pause=200, resume=200, chat-resumed=200
* Known gap: paused chat still 200 (host auth cache delay, not CRM code)
This commit is contained in:
phamnazage-jpg
2026-06-06 22:25:46 +08:00
parent 47a67eb663
commit 6eec70d6a3
7 changed files with 435 additions and 18 deletions

View File

@@ -4,8 +4,10 @@ import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
@@ -911,6 +913,49 @@ func TestEnsureSubscriptionAccessManagedProbeWithMock(t *testing.T) {
}
}
func TestPauseResumeManagedSubscriptionAccessWithMock(t *testing.T) {
t.Parallel()
var payloads []string
expected := buildManagedSubscriptionIdentity("portal-user:13", "101")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.RequestURI(), "/api/v1/admin/users?"):
if !strings.Contains(r.URL.RawQuery, url.QueryEscape(expected.Email)) {
t.Fatalf("search query = %q, want %q", r.URL.RawQuery, expected.Email)
}
w.Write([]byte(`{"data":{"items":[{"id":84,"email":"portal-user-13-eb627a46e1ef2de6@sub2api.local"}]}}`))
case r.Method == http.MethodPut && r.URL.Path == "/api/v1/admin/users/84":
body, err := io.ReadAll(r.Body)
if err != nil {
t.Fatalf("read update body: %v", err)
}
payloads = append(payloads, string(body))
w.Write([]byte(`{"data":{"id":84}}`))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
client, _ := NewClient(srv.URL, WithBearerToken("admin-token"))
if err := client.PauseManagedSubscriptionAccess(context.Background(), "portal-user:13", "101"); err != nil {
t.Fatalf("PauseManagedSubscriptionAccess() error = %v", err)
}
if err := client.ResumeManagedSubscriptionAccess(context.Background(), "portal-user:13", "101"); err != nil {
t.Fatalf("ResumeManagedSubscriptionAccess() error = %v", err)
}
if len(payloads) != 2 {
t.Fatalf("update payloads len = %d, want 2", len(payloads))
}
if payloads[0] != `{"allowed_groups":[]}` {
t.Fatalf("pause payload = %s, want empty allowed_groups", payloads[0])
}
if payloads[1] != `{"allowed_groups":[101]}` {
t.Fatalf("resume payload = %s, want restored group 101", payloads[1])
}
}
func TestEnsureSubscriptionAccessRealUserProbeWithMock(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {