chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package sharing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"data-terminal-cloud/internal/identity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type sharingIntegrationStore struct{}
|
||||
|
||||
func (*sharingIntegrationStore) PresignMediaDownload(context.Context, string, time.Duration, string, string, bool) (string, error) {
|
||||
return "https://objects.invalid/shared-media", nil
|
||||
}
|
||||
|
||||
func TestTranscriptSharingLoginAndAnonymousLifecycle(t *testing.T) {
|
||||
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
||||
if databaseURL == "" {
|
||||
t.Skip("TEST_DATABASE_URL is not set")
|
||||
}
|
||||
ctx := context.Background()
|
||||
pool, err := pgxpool.New(ctx, databaseURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pool.Close()
|
||||
if _, err := NewService(nil, audit.NewRepository(), &sharingIntegrationStore{}, []byte(strings.Repeat("s", 32)), time.Minute); err == nil {
|
||||
t.Fatal("nil database pool was accepted")
|
||||
}
|
||||
service, err := NewService(pool, audit.NewRepository(), &sharingIntegrationStore{}, []byte(strings.Repeat("s", 32)), 5*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
owner := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
editor := identity.Principal{Issuer: owner.Issuer, Subject: "00000000-0000-0000-0000-000000000102"}
|
||||
outsider := identity.Principal{Issuer: owner.Issuer, Subject: "00000000-0000-0000-0000-000000000103"}
|
||||
contextID, contextPublicID := uuid.NewString(), uuid.NewString()
|
||||
conversationID, conversationPublicID := uuid.NewString(), uuid.NewString()
|
||||
loginKey, anonymousKey := "sharing-integration-login-"+uuid.NewString(), "sharing-integration-anonymous-"+uuid.NewString()
|
||||
grantIDs := []string{}
|
||||
defer func() {
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE request_id LIKE 'sharing-integration-%'`)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM idempotency_record WHERE scope='sharing-grant.create' AND idempotency_key=ANY($1::text[])`, []string{loginKey, anonymousKey})
|
||||
if len(grantIDs) > 0 {
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM sharing_grant WHERE public_id=ANY($1::uuid[])`, grantIDs)
|
||||
}
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation_turn WHERE conversation_id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation WHERE id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM context_snapshot WHERE id=$1::uuid`, contextID)
|
||||
}()
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO context_snapshot(id,public_id,tenant_id,subject_id,profile_version,consent_snapshot_id,memory_versions,prompt_bundle_version,engine_mode,content_digest,sanitized_summary)
|
||||
VALUES($1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000401',1,
|
||||
'10000000-0000-0000-0000-000000000701','[]','sharing-integration-v1','s2s_hybrid',$3,'合成分享上下文')`,
|
||||
contextID, contextPublicID, strings.Repeat("f", 64)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO conversation(id,public_id,tenant_id,household_id,subject_id,device_id,consent_snapshot_id,context_snapshot_id,
|
||||
provider_task_ref,engine_mode,retention_mode,status,allocation_expires_at,started_at,ended_at,stop_reason)
|
||||
VALUES($1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000201',
|
||||
'10000000-0000-0000-0000-000000000401','10000000-0000-0000-0000-000000000501',
|
||||
'10000000-0000-0000-0000-000000000701',$3::uuid,$4,'s2s_hybrid','transcript_only','COMPLETED',now(),now()-interval '2 minutes',now(),'user_hangup')`,
|
||||
conversationID, conversationPublicID, contextID, "sharing-task-"+conversationID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO conversation_turn(tenant_id,subject_id,conversation_id,turn_key,sequence_no,speaker,start_at,end_at,final_text,source_type)
|
||||
VALUES('10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000401',$1::uuid,$2,1,'subject',now()-interval '90 seconds',now()-interval '80 seconds',$3,'provider')`,
|
||||
conversationID, "sharing-turn-"+conversationID, "这是可分享的合成话轮。"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := normalizeInput(CreateInput{ResourceType: "story", ResourceID: uuid.NewString(), GranteeUserID: "10000000-0000-0000-0000-000000000112", Permission: "edit", Purpose: "禁止匿名编辑", ExpiresAt: time.Now().Add(time.Hour), LoginRequired: false}); !errors.Is(err, ErrInvalid) {
|
||||
t.Fatalf("anonymous edit validation error=%v", err)
|
||||
}
|
||||
|
||||
loginInput := CreateInput{ResourceType: " transcript ", ResourceID: conversationPublicID, GranteeUserID: "10000000-0000-0000-0000-000000000112", Permission: " view_text ", Purpose: "家庭回顾", ExpiresAt: time.Now().Add(time.Hour), LoginRequired: true}
|
||||
loginGrant, created, err := service.Create(ctx, owner, loginKey, "sharing-integration-login-create", loginInput)
|
||||
if err != nil || !created || loginGrant.AccessToken != "" || loginGrant.ResourceType != "TRANSCRIPT" || loginGrant.Permission != "VIEW_TEXT" {
|
||||
t.Fatalf("login grant=%+v created=%v err=%v", loginGrant, created, err)
|
||||
}
|
||||
grantIDs = append(grantIDs, loginGrant.ID)
|
||||
replayedGrant, created, err := service.Create(ctx, owner, loginKey, "sharing-integration-login-replay", loginInput)
|
||||
if err != nil || created || replayedGrant.ID != loginGrant.ID {
|
||||
t.Fatalf("replayed grant=%+v created=%v err=%v", replayedGrant, created, err)
|
||||
}
|
||||
grants, err := service.List(ctx, owner, "10000000-0000-0000-0000-000000000411")
|
||||
if err != nil || len(grants) != 1 || grants[0].ID != loginGrant.ID {
|
||||
t.Fatalf("grants=%+v err=%v", grants, err)
|
||||
}
|
||||
shared, err := service.Access(ctx, editor, loginGrant.ID, "sharing-integration-login-access")
|
||||
if err != nil || len(shared.Turns) != 1 || shared.Turns[0].Text != "这是可分享的合成话轮。" {
|
||||
t.Fatalf("shared transcript=%+v err=%v", shared, err)
|
||||
}
|
||||
if _, err := service.Access(ctx, owner, loginGrant.ID, "sharing-integration-recipient-mismatch"); !errors.Is(err, ErrAccessDenied) {
|
||||
t.Fatalf("grantor recipient mismatch error=%v", err)
|
||||
}
|
||||
if _, err := service.Access(ctx, outsider, loginGrant.ID, "sharing-integration-outsider"); !errors.Is(err, ErrAccessDenied) {
|
||||
t.Fatalf("outsider access error=%v", err)
|
||||
}
|
||||
if _, err := service.AccessByToken(ctx, loginGrant.ID, strings.Repeat("x", 40), "sharing-integration-login-token"); !errors.Is(err, ErrAccessDenied) {
|
||||
t.Fatalf("login grant token error=%v", err)
|
||||
}
|
||||
|
||||
anonymousInput := loginInput
|
||||
anonymousInput.LoginRequired = false
|
||||
anonymousInput.Purpose = "匿名家庭回顾"
|
||||
anonymousGrant, created, err := service.Create(ctx, owner, anonymousKey, "sharing-integration-anonymous-create", anonymousInput)
|
||||
if err != nil || !created || len(anonymousGrant.AccessToken) < 32 {
|
||||
t.Fatalf("anonymous grant=%+v created=%v err=%v", anonymousGrant, created, err)
|
||||
}
|
||||
grantIDs = append(grantIDs, anonymousGrant.ID)
|
||||
if _, err := service.AccessByToken(ctx, anonymousGrant.ID, "wrong-token-value-that-is-long-enough", "sharing-integration-bad-token"); !errors.Is(err, ErrAccessDenied) {
|
||||
t.Fatalf("bad anonymous token error=%v", err)
|
||||
}
|
||||
shared, err = service.AccessByToken(ctx, anonymousGrant.ID, anonymousGrant.AccessToken, "sharing-integration-token-access")
|
||||
if err != nil || len(shared.Turns) != 1 {
|
||||
t.Fatalf("anonymous shared transcript=%+v err=%v", shared, err)
|
||||
}
|
||||
revoked, err := service.Revoke(ctx, owner, anonymousGrant.ID, "sharing-integration-revoke")
|
||||
if err != nil || revoked.RevokedAt == nil || revoked.Version != 2 {
|
||||
t.Fatalf("revoked grant=%+v err=%v", revoked, err)
|
||||
}
|
||||
if _, err := service.AccessByToken(ctx, anonymousGrant.ID, anonymousGrant.AccessToken, "sharing-integration-revoked-token"); !errors.Is(err, ErrExpired) {
|
||||
t.Fatalf("revoked token error=%v", err)
|
||||
}
|
||||
var successLogs, deniedLogs int
|
||||
if err := pool.QueryRow(ctx, `SELECT count(*) FILTER(WHERE result='SUCCESS'),count(*) FILTER(WHERE result='DENIED') FROM sharing_access_log WHERE sharing_grant_id IN (SELECT id FROM sharing_grant WHERE public_id=ANY($1::uuid[]))`, grantIDs).Scan(&successLogs, &deniedLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if successLogs != 2 || deniedLogs != 5 {
|
||||
t.Fatalf("sharing access logs success=%d denied=%d", successLogs, deniedLogs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user