168 lines
8.3 KiB
Go
168 lines
8.3 KiB
Go
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))
|
|
}
|