| 1 | package bot |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/base64" |
| 6 | "net/http" |
| 7 | "net/http/httptest" |
| 8 | "os" |
| 9 | "path/filepath" |
| 10 | "strings" |
| 11 | "testing" |
| 12 | ) |
| 13 | |
| 14 | func TestSaveInboundMediaStoresWorkspaceImageAttachment(t *testing.T) { |
| 15 | raw, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=") |
| 16 | if err != nil { |
| 17 | t.Fatalf("decode png: %v", err) |
| 18 | } |
| 19 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 20 | w.Header().Set("Content-Type", "application/octet-stream") |
| 21 | _, _ = w.Write(raw) |
| 22 | })) |
| 23 | defer srv.Close() |
| 24 | workspace := t.TempDir() |
| 25 | |
| 26 | ref, err := saveOneInboundMedia(context.Background(), workspace, srv.URL+"/shot.png") |
| 27 | if err != nil { |
| 28 | t.Fatalf("saveOneInboundMedia: %v", err) |
| 29 | } |
| 30 | if !strings.HasPrefix(ref, ".reasonix/attachments/") || !strings.HasSuffix(ref, ".png") { |
| 31 | t.Fatalf("ref = %q, want png attachment ref", ref) |
| 32 | } |
| 33 | if _, err := os.Stat(filepath.Join(workspace, filepath.FromSlash(ref))); err != nil { |
| 34 | t.Fatalf("stored attachment missing: %v", err) |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | func TestSaveInboundMediaItemsStoresBytesAndReportsErrors(t *testing.T) { |
| 39 | png, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=") |
| 40 | if err != nil { |
| 41 | t.Fatalf("decode png: %v", err) |
| 42 | } |
| 43 | workspace := t.TempDir() |
| 44 | |
| 45 | refs, fallbacks, errs := saveInboundMediaItems(context.Background(), workspace, []InboundMedia{ |
| 46 | {MIME: "image/png", Data: png}, |
| 47 | {Name: "notes.txt", MIME: "text/plain", Data: []byte("hello from feishu")}, |
| 48 | {Name: "empty.bin", FailureText: "[file unavailable]"}, // no data -> error |
| 49 | }) |
| 50 | if len(errs) != 1 { |
| 51 | t.Fatalf("errs = %v, want exactly one for the empty item", errs) |
| 52 | } |
| 53 | if len(refs) != 2 { |
| 54 | t.Fatalf("refs = %v, want image and text attachment", refs) |
| 55 | } |
| 56 | if len(fallbacks) != 1 || fallbacks[0] != "[file unavailable]" { |
| 57 | t.Fatalf("fallbacks = %v, want the failed item's placeholder", fallbacks) |
| 58 | } |
| 59 | if !strings.HasSuffix(refs[0], ".png") { |
| 60 | t.Fatalf("image ref = %q, want .png attachment", refs[0]) |
| 61 | } |
| 62 | if !strings.HasSuffix(refs[1], ".txt") { |
| 63 | t.Fatalf("text ref = %q, want .txt attachment", refs[1]) |
| 64 | } |
| 65 | for _, ref := range refs { |
| 66 | if _, err := os.Stat(filepath.Join(workspace, filepath.FromSlash(ref))); err != nil { |
| 67 | t.Fatalf("stored attachment missing: %v", err) |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | func TestSaveInboundMediaItemsLoadsDeferredBytesAfterAdmission(t *testing.T) { |
| 73 | workspace := t.TempDir() |
| 74 | called := false |
| 75 | refs, fallbacks, errs := saveInboundMediaItems(context.Background(), workspace, []InboundMedia{{ |
| 76 | FailureText: "[download failed]", |
| 77 | Load: func(context.Context) ([]byte, string, error) { |
| 78 | called = true |
| 79 | return []byte("deferred data"), "notes.txt", nil |
| 80 | }, |
| 81 | }}) |
| 82 | if !called { |
| 83 | t.Fatal("deferred loader was not called") |
| 84 | } |
| 85 | if len(errs) != 0 || len(fallbacks) != 0 || len(refs) != 1 || !strings.HasSuffix(refs[0], ".txt") { |
| 86 | t.Fatalf("refs/fallbacks/errs = %v/%v/%v, want one saved text attachment", refs, fallbacks, errs) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | func TestInputTextWithMediaKeepsDeferredFailurePlaceholder(t *testing.T) { |
| 91 | workspace := t.TempDir() |
| 92 | adapter := newFakeAdapter(PlatformFeishu, "feishu") |
| 93 | gw := NewGateway(GatewayConfig{WorkspaceRoot: workspace}, map[Platform]Adapter{PlatformFeishu: adapter}, discardLogger()) |
| 94 | input := gw.inputTextWithMedia(context.Background(), adapter, InboundMessage{ |
| 95 | Platform: PlatformFeishu, |
| 96 | ChatType: ChatDM, |
| 97 | ChatID: "chat", |
| 98 | Media: []InboundMedia{{ |
| 99 | FailureText: "[文件下载失败: report.pdf]", |
| 100 | Load: func(context.Context) ([]byte, string, error) { |
| 101 | return nil, "", os.ErrNotExist |
| 102 | }, |
| 103 | }}, |
| 104 | }, nil) |
| 105 | if input != "[文件下载失败: report.pdf]" { |
| 106 | t.Fatalf("input = %q, want deferred failure placeholder", input) |
| 107 | } |
| 108 | } |
| 109 |