返回 DeepSeek-Reasonix
image_replay_test.go
根目录 / internal / control / image_replay_test.go
1 package control
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "os"
9 "path/filepath"
10 "strings"
11 "testing"
12
13 "reasonix/internal/attachment"
14 "reasonix/internal/config"
15 "reasonix/internal/provider"
16 )
17
18 func TestHistoricalImageLossDoesNotBlockTextOrHealthyImages(t *testing.T) {
19 for _, damage := range []string{"missing", "corrupt", "metadata"} {
20 t.Run(damage, func(t *testing.T) {
21 root := t.TempDir()
22 c := newOwnedTestController(t, Options{WorkspaceRoot: root, SessionPath: filepath.Join(root, "session.jsonl"), ImageRouteConfig: config.Default()})
23 draft, err := c.StageImage(t.Context(), "old.png", "image/png", "data:image/png;base64,"+tinyPNG)
24 if err != nil {
25 t.Fatal(err)
26 }
27 digest := draft.Ref.Content.Digest
28 object := filepath.Join(c.attachmentService().Store().Root(), "objects", digest[:2], digest[2:4], digest)
29 switch damage {
30 case "missing":
31 err = os.Remove(object)
32 case "corrupt":
33 err = os.WriteFile(object, []byte("corrupt"), 0o600)
34 case "metadata":
35 draft.Ref.Width = 0
36 }
37 if err != nil {
38 t.Fatal(err)
39 }
40 messages := []provider.Message{
41 {ID: "old", Role: provider.RoleUser, Content: "old text", ImageInputs: []attachment.ImageInput{
42 {Kind: attachment.KindAttachment, Attachment: &draft.Ref},
43 {Kind: attachment.KindURL, URL: "https://example.invalid/healthy.png"},
44 }},
45 {ID: "next", Role: provider.RoleUser, Content: "continue with text"},
46 }
47 before, _ := json.Marshal(messages)
48 got, err := c.ResolveRequestImagesForModel(t.Context(), messages, "fixture/vision", true)
49 if err != nil {
50 t.Fatal(err)
51 }
52 if len(got[0].Images) != 1 || !strings.Contains(got[0].Content, "unavailable") || got[1].Content != messages[1].Content {
53 t.Fatalf("recovered messages = %+v", got)
54 }
55 after, _ := json.Marshal(messages)
56 if !bytes.Equal(before, after) {
57 t.Fatal("recovery rewrote canonical image references")
58 }
59 if _, err := c.ResolveRequestImagesForModel(t.Context(), messages[:1], "fixture/vision", true); err == nil {
60 t.Fatal("current-turn image failure was ignored")
61 }
62 ctx, cancel := context.WithCancel(t.Context())
63 cancel()
64 if _, err := c.ResolveRequestImagesForModel(ctx, messages, "fixture/vision", true); !errors.Is(err, context.Canceled) {
65 t.Fatalf("cancellation was swallowed: %v", err)
66 }
67 })
68 }
69 }
70
71 func TestImageRouteConfigurationErrorIsNotCached(t *testing.T) {
72 root := t.TempDir()
73 // NUL makes path resolution fail on every supported OS. A regular file
74 // used as a parent is ENOTDIR on Unix but missing-path fallback on Windows.
75 c := &Controller{workspaceRoot: filepath.Join(root, "invalid\x00root")}
76 if _, err := c.imageRequestRoute("custom/vision-pro"); err == nil {
77 t.Fatal("unreadable configuration accepted")
78 }
79 // Correct the source and retry on the same controller: the failed load
80 // must not mark its route snapshot ready or memoize the error.
81 c.workspaceRoot = root
82 writeVisionTestConfig(t, root)
83 got, err := c.imageRequestRoute("custom/vision-pro")
84 if err != nil || got.BaseURL != "https://example.invalid/v1" {
85 t.Fatalf("corrected config remained blocked: %+v, %v", got, err)
86 }
87 }
88
88 lines GO