WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package workbench
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTaskDurationContractRejectsInvalidValues(t *testing.T) {
|
||||
for _, value := range []any{-1, 1441, 1.5, "60"} {
|
||||
r := &Resource{Kind: "content-tasks", Name: "Task", Data: JSON{"duration_minutes": value}}
|
||||
if err := NewService(nil).validate(nil, r); !errors.Is(err, ErrInvalid) {
|
||||
t.Fatalf("invalid duration accepted: %v: %v", value, err)
|
||||
}
|
||||
}
|
||||
for _, value := range []int{0, 1, 60, 1440} {
|
||||
if err := validateTaskDuration(value); err != nil {
|
||||
t.Fatalf("valid duration rejected: %d: %v", value, err)
|
||||
}
|
||||
}
|
||||
d, err := decode[TaskData](JSON{})
|
||||
if err != nil || d.DurationMinutes != 0 || taskDurationInstructions(d, -1) != "" || taskDurationReviewInstructions(d) != "" {
|
||||
t.Fatal("legacy tasks must retain unspecified duration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompositionUsesAndPreservesTaskDuration(t *testing.T) {
|
||||
s, ctx, job, model := generationHarness(t)
|
||||
flow := &Resource{ID: "flow", TenantID: 1, OwnerID: "writer", Kind: "flows", Name: "Flow", Version: 1, Data: asJSON(FlowData{Nodes: []FlowNode{{ID: "n1", AgentID: "author"}}})}
|
||||
if err := s.db.Create(flow).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := &Resource{Kind: "content-tasks", Name: "Task", Data: asJSON(TaskData{DurationMinutes: 120, SourceText: "Framework", FlowID: flow.ID})}
|
||||
job.Action = "decompose"
|
||||
model.output = `{"segments":[{"id":"s1","title":"Foundation","agent_id":"author","node_id":"n1"}]}`
|
||||
if err := s.executeTask(ctx, job, r, ActionRequest{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, err := decode[TaskData](r.Data)
|
||||
if err != nil || d.DurationMinutes != 120 || len(d.Segments) != 1 || !strings.Contains(model.messages[1].Content, "直播目标总时长:120 分钟") {
|
||||
t.Fatalf("duration lost during decomposition: %+v %v", d, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSegmentGenerationAndRevisionUseTotalDurationWithoutSkippedContent(t *testing.T) {
|
||||
for _, action := range []string{"generate", "revise"} {
|
||||
t.Run(action, func(t *testing.T) {
|
||||
s, ctx, job, model := generationHarness(t)
|
||||
job.Action = action
|
||||
d := TaskData{DurationMinutes: 120, AgentID: "author", Segments: []Segment{
|
||||
{ID: "a", Title: "Foundation", Status: "accepted", Content: "Accepted context"},
|
||||
{ID: "b", Title: "Excluded", Status: "skipped", Content: "Skipped private draft"},
|
||||
{ID: "c", Title: "Application", AgentID: "author", Status: "generated", Content: "Current draft"},
|
||||
}}
|
||||
r := &Resource{Kind: "content-tasks", Data: asJSON(d)}
|
||||
if err := s.executeTask(ctx, job, r, ActionRequest{SegmentID: "c", Advice: "Clearer"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prompt := model.messages[1].Content
|
||||
for _, expected := range []string{"直播目标总时长:120 分钟", "2 个未跳过", "参考时长约 60.0 分钟", "Accepted context"} {
|
||||
if !strings.Contains(prompt, expected) {
|
||||
t.Fatalf("generation prompt missing %q", expected)
|
||||
}
|
||||
}
|
||||
if strings.Contains(prompt, "Skipped private draft") {
|
||||
t.Fatal("skipped content returned to generation")
|
||||
}
|
||||
stored, _ := decode[TaskData](r.Data)
|
||||
if stored.DurationMinutes != 120 {
|
||||
t.Fatal("generation dropped duration")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationChangesPreserveTextAndRequireFreshQualityReview(t *testing.T) {
|
||||
s, ctx, job, model := generationHarness(t)
|
||||
job.Action = "review"
|
||||
d := TaskData{DurationMinutes: 75, AgentID: "author", Segments: []Segment{{ID: "s1", AgentID: "author", Title: "Foundation", Content: "Accepted manuscript", Status: "accepted"}}}
|
||||
r := &Resource{Kind: "content-tasks", Data: asJSON(d)}
|
||||
model.output = `{"passed":true,"summary":"Reviewed","issues":[]}`
|
||||
if err := s.executeTask(ctx, job, r, ActionRequest{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, _ = decode[TaskData](r.Data)
|
||||
accepted, _ := acceptedManuscript(d)
|
||||
if d.Quality.DurationMinutes != 75 || requireQuality(d, accepted) != nil || !strings.Contains(model.messages[1].Content, "75 分钟") || !strings.Contains(model.messages[1].Content, "不得把局部窗口视作完整任务") {
|
||||
t.Fatal("quality review omitted duration context")
|
||||
}
|
||||
job.Action = "compose"
|
||||
if err := s.executeTask(ctx, job, r, ActionRequest{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, _ = decode[TaskData](r.Data)
|
||||
if d.Composed != "Accepted manuscript" || d.Review == "" {
|
||||
t.Fatal("fixture must contain both a composition and a quality review")
|
||||
}
|
||||
d.DurationMinutes = 90
|
||||
if requireQuality(d, accepted) == nil {
|
||||
t.Fatal("old duration approval reused")
|
||||
}
|
||||
if err := applyManualUpdate(r, resourceRequest{Name: "Task", Data: asJSON(d)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updated, _ := decode[TaskData](r.Data)
|
||||
if updated.DurationMinutes != 90 || updated.Segments[0].Content != "Accepted manuscript" || updated.Segments[0].Status != "needs_review" || updated.Quality != nil || updated.Review != "" || updated.Composed != "" || updated.Segments[0].Review != "" || r.Status != "needs_review" {
|
||||
t.Fatalf("duration change did not preserve and invalidate content correctly: %+v", updated)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user