chore: establish local platform baseline

This commit is contained in:
Codex Local
2026-08-26 06:41:54 +08:00
commit 29bdf4900f
575 changed files with 95644 additions and 0 deletions
@@ -0,0 +1,167 @@
package httpapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/concurrency"
"data-terminal-cloud/internal/identity"
"data-terminal-cloud/internal/saas"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestSaaSHouseholdAndSubjectWriteHandlers(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not set")
}
pool, err := pgxpool.New(context.Background(), databaseURL)
if err != nil {
t.Fatal(err)
}
defer pool.Close()
service := saas.NewService(pool, audit.NewRepository(), nil, nil)
principal := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
var householdID, subjectID, factID, consentID string
defer func() {
if subjectID != "" {
if consentID != "" {
_, _ = pool.Exec(context.Background(), `DELETE FROM audit_event WHERE resource_id=$1::uuid`, consentID)
}
if factID != "" {
_, _ = pool.Exec(context.Background(), `DELETE FROM audit_event WHERE resource_id=$1::uuid`, factID)
}
_, _ = pool.Exec(context.Background(), `DELETE FROM consent_snapshot WHERE subject_id=(SELECT id FROM subject_profile WHERE public_id=$1::uuid)`, subjectID)
_, _ = pool.Exec(context.Background(), `DELETE FROM consent_record WHERE subject_id=(SELECT id FROM subject_profile WHERE public_id=$1::uuid)`, subjectID)
_, _ = pool.Exec(context.Background(), `DELETE FROM subject_fact WHERE subject_id=(SELECT id FROM subject_profile WHERE public_id=$1::uuid)`, subjectID)
_, _ = pool.Exec(context.Background(), `DELETE FROM audit_event WHERE resource_id=$1::uuid`, subjectID)
_, _ = pool.Exec(context.Background(), `DELETE FROM subject_profile WHERE public_id=$1::uuid`, subjectID)
}
if householdID != "" {
_, _ = pool.Exec(context.Background(), `DELETE FROM audit_event WHERE resource_id=$1::uuid`, householdID)
_, _ = pool.Exec(context.Background(), `DELETE FROM household_member WHERE household_id=(SELECT id FROM household WHERE public_id=$1::uuid)`, householdID)
_, _ = pool.Exec(context.Background(), `DELETE FROM household WHERE public_id=$1::uuid`, householdID)
}
}()
request := authenticatedRequest(http.MethodPost, "/api/v1/households", `{"name":"HTTP写接口集成家庭"}`, principal)
request.Header.Set("X-Tenant-Id", "10000000-0000-0000-0000-000000000010")
recorder := httptest.NewRecorder()
CreateHouseholdHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated || recorder.Header().Get("ETag") != concurrency.FormatETag(1) {
t.Fatalf("create household status=%d headers=%v body=%s", recorder.Code, recorder.Header(), recorder.Body.String())
}
var householdResponse struct {
Data saas.Household `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &householdResponse); err != nil {
t.Fatal(err)
}
householdID = householdResponse.Data.ID
request = authenticatedRequest(http.MethodPatch, "/api/v1/households/"+householdID, `{"name":"HTTP写接口集成家庭-修改"}`, principal)
request.SetPathValue("id", householdID)
recorder = httptest.NewRecorder()
PatchHouseholdHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusPreconditionRequired {
t.Fatalf("missing household ETag status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = authenticatedRequest(http.MethodPatch, "/api/v1/households/"+householdID, `{"name":"HTTP写接口集成家庭-修改"}`, principal)
request.SetPathValue("id", householdID)
request.Header.Set("If-Match", concurrency.FormatETag(1))
recorder = httptest.NewRecorder()
PatchHouseholdHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK || recorder.Header().Get("ETag") != concurrency.FormatETag(2) {
t.Fatalf("patch household status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = authenticatedRequest(http.MethodPost, "/api/v1/households/"+householdID+"/subjects", `{"preferred_name":"HTTP集成长者","language":"zh-CN","timezone":"Asia/Shanghai","profile_data":{"synthetic":true},"conversation_preferences":{"pace":"slow"}}`, principal)
request.SetPathValue("id", householdID)
recorder = httptest.NewRecorder()
CreateSubjectHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated || recorder.Header().Get("ETag") != concurrency.FormatETag(1) {
t.Fatalf("create subject status=%d body=%s", recorder.Code, recorder.Body.String())
}
var subjectResponse struct {
Data saas.Subject `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &subjectResponse); err != nil {
t.Fatal(err)
}
subjectID = subjectResponse.Data.ID
request = authenticatedRequest(http.MethodPatch, "/api/v1/subjects/"+subjectID, `{"preferred_name":"HTTP集成长者-修改"}`, principal)
request.SetPathValue("id", subjectID)
request.Header.Set("If-Match", concurrency.FormatETag(1))
recorder = httptest.NewRecorder()
PatchSubjectHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK || recorder.Header().Get("ETag") != concurrency.FormatETag(2) {
t.Fatalf("patch subject status=%d body=%s", recorder.Code, recorder.Body.String())
}
var patchedSubject struct {
Data saas.Subject `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &patchedSubject); err != nil || patchedSubject.Data.PreferredName != "HTTP集成长者-修改" {
t.Fatalf("patched subject=%+v err=%v", patchedSubject, err)
}
request = authenticatedRequest(http.MethodPost, "/api/v1/subjects/"+subjectID+"/facts", `{"fact_type":"hometown","content":"合成城市","source_type":"family_entry","source_ref":"http-integration://source","confirmation_status":"pending","sensitivity":"normal"}`, principal)
request.SetPathValue("id", subjectID)
recorder = httptest.NewRecorder()
CreateSubjectFactHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated || recorder.Header().Get("ETag") != concurrency.FormatETag(1) {
t.Fatalf("create fact status=%d body=%s", recorder.Code, recorder.Body.String())
}
var factResponse struct {
Data saas.SubjectFact `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &factResponse); err != nil {
t.Fatal(err)
}
factID = factResponse.Data.ID
request = authenticatedRequest(http.MethodPatch, "/api/v1/subjects/"+subjectID+"/facts/"+factID, `{"confirmation_status":"confirmed"}`, principal)
request.SetPathValue("id", subjectID)
request.SetPathValue("fact_id", factID)
request.Header.Set("If-Match", concurrency.FormatETag(1))
recorder = httptest.NewRecorder()
PatchSubjectFactHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK || recorder.Header().Get("ETag") != concurrency.FormatETag(2) {
t.Fatalf("patch fact status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = authenticatedRequest(http.MethodPost, "/api/v1/subjects/"+subjectID+"/consents", `{"type":"story_extraction","scope":{"future":true},"policy_version":"local-v1","notice_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","evidence_ref":"http-integration://consent"}`, principal)
request.SetPathValue("id", subjectID)
recorder = httptest.NewRecorder()
GrantConsentHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Fatalf("grant consent status=%d body=%s", recorder.Code, recorder.Body.String())
}
var consentResponse struct {
Data saas.Consent `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &consentResponse); err != nil {
t.Fatal(err)
}
consentID = consentResponse.Data.ID
request = authenticatedRequest(http.MethodPost, "/api/v1/subjects/"+subjectID+"/consents/"+consentID+":withdraw", ``, principal)
request.SetPathValue("id", subjectID)
request.SetPathValue("consent_id", consentID)
recorder = httptest.NewRecorder()
WithdrawConsentHandler(service).ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("withdraw consent status=%d body=%s", recorder.Code, recorder.Body.String())
}
}
func authenticatedRequest(method, target, body string, principal identity.Principal) *http.Request {
request := httptest.NewRequest(method, target, strings.NewReader(body))
request.Header.Set("Content-Type", "application/json")
return request.WithContext(identity.WithPrincipal(request.Context(), principal))
}