chore: establish local platform baseline
This commit is contained in:
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user