package attachments import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func TestSanitizeFilename(t *testing.T) { cases := map[string]string{ "proposal.md": "proposal.md", "../etc/passwd": "passwd", "": "attachment.bin", "...": "attachment.bin", "a?b=c": "a", "weird name.png": "weird name.png", "name%20encoded.txt": "name encoded.txt", } for in, want := range cases { got := sanitizeFilename(in) if got != want { t.Errorf("sanitizeFilename(%q) = %q, want %q", in, got, want) } } } func TestSanitizeID(t *testing.T) { cases := map[string]string{ "m-abc-123": "m-abc-123", "../../etc/passwd": "______etc_passwd", "": "msg", "with space": "with_space", } for in, want := range cases { got := sanitizeID(in) if got != want { t.Errorf("sanitizeID(%q) = %q, want %q", in, got, want) } } } func TestAppendFooterEmpty(t *testing.T) { got := AppendFooter("hello", nil) if got != "hello" { t.Errorf("empty list should preserve body, got %q", got) } } func TestAppendFooterFormats(t *testing.T) { files := []DownloadResult{ {LocalPath: "/tmp/a.md", Name: "a.md", MimeType: "text/markdown", Size: 42}, {LocalPath: "/tmp/b.png", Name: "b.png", MimeType: "", Size: 1024}, } out := AppendFooter("hello\nworld", files) if !strings.Contains(out, "hello\nworld\n\nAttachments:\n") { t.Errorf("footer placement wrong: %q", out) } if !strings.Contains(out, "- /tmp/a.md (text/markdown, 42 bytes)") { t.Errorf("first entry: %q", out) } if !strings.Contains(out, "- /tmp/b.png (application/octet-stream, 1024 bytes)") { t.Errorf("default mime: %q", out) } } func TestFetchAllHappyPath(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write([]byte("file contents " + r.URL.Path)) })) defer srv.Close() d := New(filepath.Join(t.TempDir(), "att")) files := d.FetchAll(context.Background(), "tok", "msg-1", []AttachmentRef{ {URL: srv.URL + "/foo.md", Name: "foo.md", MimeType: "text/markdown"}, {URL: srv.URL + "/bar.png", Name: "bar.png"}, }) if len(files) != 2 { t.Fatalf("len = %d", len(files)) } if !strings.HasSuffix(files[0].LocalPath, "/msg-1/foo.md") { t.Errorf("path = %s", files[0].LocalPath) } raw, _ := os.ReadFile(files[0].LocalPath) if !strings.HasPrefix(string(raw), "file contents") { t.Errorf("file content = %q", raw) } } func TestFetchAllSkipsErrors(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "ok") { w.Write([]byte("ok")) } else { w.WriteHeader(404) } })) defer srv.Close() d := New(filepath.Join(t.TempDir(), "att")) files := d.FetchAll(context.Background(), "", "m", []AttachmentRef{ {URL: srv.URL + "/ok.txt", Name: "ok.txt"}, {URL: srv.URL + "/missing.txt", Name: "missing.txt"}, }) if len(files) != 1 { t.Errorf("expected 1 result (404 skipped), got %d", len(files)) } } func TestFetchAllEmptyListNoMkdir(t *testing.T) { d := New(filepath.Join(t.TempDir(), "att")) got := d.FetchAll(context.Background(), "", "m", nil) if got != nil { t.Errorf("empty → nil, got %v", got) } } func TestFetchAllSendsAuthHeader(t *testing.T) { gotAuth := "" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("authorization") w.Write([]byte("x")) })) defer srv.Close() d := New(filepath.Join(t.TempDir(), "att")) d.FetchAll(context.Background(), "the-token", "m", []AttachmentRef{ {URL: srv.URL + "/a", Name: "a"}, }) if gotAuth != "Bearer the-token" { t.Errorf("auth header = %q", gotAuth) } }