Unify auth middleware and remove X-User-ID trust, add project_members authorization, sandbox workspaces via resolver, and admin-only reconcile by run_id. Introduce goose migrations with startup schema gate, fail closed when DB unavailable outside explicit ALLOW_IN_MEMORY, bundle web/dist in app Dockerfile, and remove frontend pseudo-login fallback.
175 lines
4.7 KiB
Go
175 lines
4.7 KiB
Go
package bizdev
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/config"
|
|
)
|
|
|
|
func TestNormalizeDocID_LegacyPatterns(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
project int64
|
|
taskKey string
|
|
expected string
|
|
}{
|
|
{"doc-p56-research", 56, "research", "doc_proj56_research"},
|
|
{"doc-proj56-research", 56, "research", "doc_proj56_research"},
|
|
{"doc_proj56_research", 56, "research", "doc_proj56_research"},
|
|
{"", 56, "research", "doc_proj56_research"},
|
|
{"", 0, "research", ""},
|
|
}
|
|
for _, tc := range tests {
|
|
got := normalizeDocID(tc.in, tc.project, tc.taskKey)
|
|
if got != tc.expected {
|
|
t.Fatalf("normalizeDocID(%q) = %q, want %q", tc.in, got, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResolveAndValidateDocID_LegacyFallbackWhenNotRegistered(t *testing.T) {
|
|
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
|
|
ctx := context.Background()
|
|
|
|
got, err := svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
|
|
DocID: "doc-p56-research",
|
|
ProjectID: 56,
|
|
TaskKey: "research",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != "doc_proj56_research" {
|
|
t.Fatalf("got %q, want doc_proj56_research", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveAndValidateDocID_ExistingDocMismatchRejected(t *testing.T) {
|
|
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
|
|
ctx := context.Background()
|
|
|
|
docID := "doc_proj56_research"
|
|
_, err := svc.DocGov().CreateOrGetDocument(
|
|
ctx, docID, 56, "research", "调研报告", "V0.5", "",
|
|
1, "张工", 2, "李主管", 3, "王总监",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("CreateOrGetDocument: %v", err)
|
|
}
|
|
|
|
_, err = svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
|
|
DocID: docID,
|
|
ProjectID: 99,
|
|
TaskKey: "research",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "does not belong to project") {
|
|
t.Fatalf("expected project mismatch error, got %v", err)
|
|
}
|
|
|
|
_, err = svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
|
|
DocID: docID,
|
|
ProjectID: 56,
|
|
TaskKey: "intake",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "task_key mismatch") {
|
|
t.Fatalf("expected task_key mismatch error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveAndValidateDocID_ExistingDocAccepted(t *testing.T) {
|
|
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
|
|
ctx := context.Background()
|
|
|
|
docID := "doc_proj56_research"
|
|
_, err := svc.DocGov().CreateOrGetDocument(
|
|
ctx, docID, 56, "research", "调研报告", "V0.5", "",
|
|
1, "张工", 2, "李主管", 3, "王总监",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("CreateOrGetDocument: %v", err)
|
|
}
|
|
|
|
got, err := svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
|
|
DocID: "doc-p56-research",
|
|
ProjectID: 56,
|
|
TaskKey: "research",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != docID {
|
|
t.Fatalf("got %q, want %q", got, docID)
|
|
}
|
|
}
|
|
|
|
func TestCreateOrGetDocument_ConcurrentSameDocID(t *testing.T) {
|
|
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
|
|
ctx := context.Background()
|
|
docID := "doc_proj77_research"
|
|
|
|
type outcome struct {
|
|
id string
|
|
err error
|
|
}
|
|
ch := make(chan outcome, 2)
|
|
create := func() {
|
|
doc, err := svc.DocGov().CreateOrGetDocument(
|
|
ctx, docID, 77, "research", "调研报告", "V0.5", "",
|
|
1, "张工", 2, "李主管", 3, "王总监",
|
|
)
|
|
id := ""
|
|
if doc != nil {
|
|
id = doc.DocID
|
|
}
|
|
ch <- outcome{id: id, err: err}
|
|
}
|
|
go create()
|
|
go create()
|
|
first := <-ch
|
|
second := <-ch
|
|
if first.err != nil {
|
|
t.Fatalf("first create failed: %v", first.err)
|
|
}
|
|
if second.err != nil {
|
|
t.Fatalf("second create failed: %v", second.err)
|
|
}
|
|
if first.id != docID || second.id != docID {
|
|
t.Fatalf("doc ids mismatch: first=%q second=%q want %q", first.id, second.id, docID)
|
|
}
|
|
doc, err := svc.DocGov().GetDocument(ctx, docID)
|
|
if err != nil || doc == nil {
|
|
t.Fatalf("expected single persisted doc %s, got err=%v doc=%v", docID, err, doc)
|
|
}
|
|
}
|
|
|
|
func TestExecuteTask_LegacyDocIDDoesNotFailValidation(t *testing.T) {
|
|
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
|
|
ctx := context.Background()
|
|
|
|
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
|
|
TaskKey: "research",
|
|
ProjectID: 56,
|
|
ProjectName: "智能体演示项目",
|
|
CustomerName: "北京水木东方",
|
|
DeviceType: "医疗器械",
|
|
Backend: "stub",
|
|
Timeout: 10 * time.Second,
|
|
DocID: "doc-p56-research",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ExecuteTask with legacy doc_id failed: %v", err)
|
|
}
|
|
if !res.Success {
|
|
if strings.Contains(res.ErrorReason, "doc_id not found") {
|
|
t.Fatalf("legacy doc_id still rejected: %s", res.ErrorReason)
|
|
}
|
|
t.Fatalf("task failed for other reason: %s", res.ErrorReason)
|
|
}
|
|
if res.DocRecord == nil || res.DocRecord.DocID != "doc_proj56_research" {
|
|
t.Fatalf("expected doc_proj56_research record, got %#v", res.DocRecord)
|
|
}
|
|
}
|