iykyka/core-backend/persona_test.go

183 lines
6.8 KiB
Go

package main
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"testing"
)
func TestPatchTwinSettingsUpdatesRelationshipTier(t *testing.T) {
server, _ := setupTestServer(t)
userID, token := mustSignup(t, server.URL, "민수")
tier := RelationshipClose
resp := patchJSONAuth(t, server.URL+"/users/"+strconv.FormatUint(uint64(userID), 10)+"/twin-settings", token, updateTwinSettingsRequest{
AutonomyLevel: AutonomyL1,
RelationshipTier: &tier,
})
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var out map[string]interface{}
json.NewDecoder(resp.Body).Decode(&out)
if out["relationship_tier"] != "close" {
t.Fatalf("expected relationship_tier close, got %v", out)
}
if out["autonomy_level"] != "L1" {
t.Fatalf("expected autonomy_level L1, got %v", out)
}
}
func TestPatchTwinSettingsRejectsInvalidRelationshipTier(t *testing.T) {
server, _ := setupTestServer(t)
userID, token := mustSignup(t, server.URL, "민수")
bad := RelationshipTier("aloof")
resp := patchJSONAuth(t, server.URL+"/users/"+strconv.FormatUint(uint64(userID), 10)+"/twin-settings", token, updateTwinSettingsRequest{
AutonomyLevel: AutonomyL0,
RelationshipTier: &bad,
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
}
func TestPatchTwinSettingsOmittingTierLeavesItUnchanged(t *testing.T) {
server, _ := setupTestServer(t)
userID, token := mustSignup(t, server.URL, "민수")
userIDStr := strconv.FormatUint(uint64(userID), 10)
tier := RelationshipClose
patchJSONAuth(t, server.URL+"/users/"+userIDStr+"/twin-settings", token, updateTwinSettingsRequest{
AutonomyLevel: AutonomyL1,
RelationshipTier: &tier,
})
// Existing autonomy-only callers must keep working exactly as before --
// omitting relationship_tier must not reset it back to the DB default.
resp := patchJSONAuth(t, server.URL+"/users/"+userIDStr+"/twin-settings", token, updateTwinSettingsRequest{
AutonomyLevel: AutonomyL2,
})
var out map[string]interface{}
json.NewDecoder(resp.Body).Decode(&out)
if out["relationship_tier"] != "close" {
t.Fatalf("expected relationship_tier to stay close, got %v", out)
}
}
func TestContactRelationshipTierOverridesGlobalDefaultInDraft(t *testing.T) {
server, _ := setupTestServer(t)
ownerID, ownerToken := mustSignup(t, server.URL, "민수")
peerID, _ := mustSignup(t, server.URL, "철수")
// Global default starts as "formal" (signup default). Override just
// this contact to "close".
closeTier := RelationshipClose
contactResp := postJSONAuth(t, server.URL+"/users/"+strconv.FormatUint(uint64(ownerID), 10)+"/contacts", ownerToken, createContactRequest{
DisplayName: "철수",
ContactUserID: &peerID,
RelationshipTier: &closeTier,
})
if contactResp.StatusCode != http.StatusOK {
t.Fatalf("create contact: %d", contactResp.StatusCode)
}
convResp := postJSONAuth(t, server.URL+"/conversations", ownerToken, createConversationRequest{
UserIDs: []uint{ownerID, peerID},
})
var conv map[string]interface{}
json.NewDecoder(convResp.Body).Decode(&conv)
convID := uint(conv["id"].(float64))
draftResp := postJSONAuth(t, server.URL+"/conversations/"+strconv.FormatUint(uint64(convID), 10)+"/draft", ownerToken, draftMessageRequest{
ContextLines: []string{"상대: 오늘 저녁에 뭐 먹을래?"},
StyleExamples: []string{"ㅇㅇ 좋지"},
})
if draftResp.StatusCode != http.StatusOK {
t.Fatalf("draft: %d", draftResp.StatusCode)
}
var draftOut draftResponse
json.NewDecoder(draftResp.Body).Decode(&draftOut)
if !strings.Contains(draftOut.Text, "tier=close") {
t.Fatalf("expected contact override tier=close in mock draft, got %v", draftOut.Text)
}
}
func TestDraftFallsBackToGlobalTierWithoutContactOverride(t *testing.T) {
server, _ := setupTestServer(t)
ownerID, ownerToken := mustSignup(t, server.URL, "민수")
peerID, _ := mustSignup(t, server.URL, "철수")
// No contact record at all -- resolveRelationshipTier must fall back to
// the signup default ("formal") rather than erroring.
convResp := postJSONAuth(t, server.URL+"/conversations", ownerToken, createConversationRequest{
UserIDs: []uint{ownerID, peerID},
})
var conv map[string]interface{}
json.NewDecoder(convResp.Body).Decode(&conv)
convID := uint(conv["id"].(float64))
draftResp := postJSONAuth(t, server.URL+"/conversations/"+strconv.FormatUint(uint64(convID), 10)+"/draft", ownerToken, draftMessageRequest{
ContextLines: []string{"상대: 내일 시간 되세요?"},
StyleExamples: []string{"네 됩니다"},
})
var draftOut draftResponse
json.NewDecoder(draftResp.Body).Decode(&draftOut)
if !strings.Contains(draftOut.Text, "tier=formal") {
t.Fatalf("expected global default tier=formal in mock draft, got %v", draftOut.Text)
}
}
func TestDraftWithoutAuthDefaultsToFormalTier(t *testing.T) {
server, db := setupTestServer(t)
conv := Conversation{IsGroup: false}
if err := db.Create(&conv).Error; err != nil {
t.Fatalf("create conversation: %v", err)
}
// No Authorization header at all -- this endpoint has never required
// one, so it must keep working (just without tier personalization).
resp := postJSON(t, server.URL+"/conversations/"+strconv.FormatUint(uint64(conv.ID), 10)+"/draft", draftMessageRequest{
ContextLines: []string{"상대: 오늘 저녁에 뭐 먹을래?"},
StyleExamples: []string{"ㅇㅇ 좋지"},
})
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var out draftResponse
json.NewDecoder(resp.Body).Decode(&out)
if !strings.Contains(out.Text, "tier=formal") {
t.Fatalf("expected default tier=formal for unauthenticated call, got %v", out.Text)
}
}
func TestGroupConversationDraftUsesGlobalTierNotContactOverride(t *testing.T) {
server, _ := setupTestServer(t)
ownerID, ownerToken := mustSignup(t, server.URL, "민수")
peerID, _ := mustSignup(t, server.URL, "철수")
closeTier := RelationshipClose
postJSONAuth(t, server.URL+"/users/"+strconv.FormatUint(uint64(ownerID), 10)+"/contacts", ownerToken, createContactRequest{
DisplayName: "철수",
ContactUserID: &peerID,
RelationshipTier: &closeTier,
})
convID := createGroup(t, server.URL, ownerToken, []uint{ownerID, peerID})
draftResp := postJSONAuth(t, server.URL+"/conversations/"+strconv.FormatUint(uint64(convID), 10)+"/draft", ownerToken, draftMessageRequest{
ContextLines: []string{"철수: 이번 주 토요일 모임 어때?"},
StyleExamples: []string{"ㅇㅇ 좋지"},
})
var draftOut draftResponse
json.NewDecoder(draftResp.Body).Decode(&draftOut)
// Group conversations ignore the per-contact override (roadmap.md
// §2.7-B: a group has more than one counterpart) and use the global
// default ("formal") instead.
if !strings.Contains(draftOut.Text, "tier=formal") {
t.Fatalf("expected group draft to use global tier=formal, got %v", draftOut.Text)
}
}