Files
Agent-System-Backend/internal/server/artifact_download_test.go
T
weichen 3a973e7ea8 Add download semantics and extension MIME fallback to artifact content
Why: generated deliverables (e.g. meeting-minutes docx) had no reliable
download path — the content endpoint set no Content-Disposition, so the
filename only survived via the front-end <a download> attribute, and
contentTypeOf returned text/plain for any artifact whose MIME column was
empty, mangling office/pdf uploads.

Evidence: artifactContent now always sets Content-Disposition (inline by
default, attachment with ?download=1) with an RFC 5987 filename* for
non-ASCII names plus an ASCII fallback; contentTypeOf consults an
explicit office-format extension table then mime.TypeByExtension before
the kind fallbacks. New tests cover disposition on both modes, the
encoded Chinese filename, and the extension/mime precedence chain;
go test ./... green.

Impact: front-ends can link direct download URLs and browsers receive
correct file types for docx/xlsx/pptx/pdf/csv/md regardless of the
stored MIME value.

Refs: task feature/chat-order-artifact-files.
2026-09-30 15:23:02 +00:00

65 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"agent-system-backend/internal/store"
core "agent-system-core"
)
// TestArtifactContentDisposition:?download=1 切换 attachment;中文名走 RFC 5987
// filename* 权威编码;默认 inline 便于浏览器内预览。
func TestArtifactContentDisposition(t *testing.T) {
srv, st := newTestServer(t)
h := srv.Handler()
a := &core.Artifact{ProjectID: "p1", Owner: store.OwnerDefault, Name: "会议纪要.docx", Kind: "document"}
if err := st.CreateArtifact(a, []byte("PK\x03\x04 docx bytes")); err != nil {
t.Fatal(err)
}
get := func(query string) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodGet, "/api/artifacts/"+a.ID+"/content"+query, nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec
}
inline := get("")
if cd := inline.Header().Get("Content-Disposition"); !strings.HasPrefix(cd, "inline") ||
!strings.Contains(cd, `filename*=UTF-8''%E4%BC%9A%E8%AE%AE%E7%BA%AA%E8%A6%81.docx`) {
t.Errorf("inline disposition mismatch: %s", cd)
}
if ct := inline.Header().Get("Content-Type"); ct != "application/vnd.openxmlformats-officedocument.wordprocessingml.document" {
t.Errorf("docx content-type fallback mismatch: %s", ct)
}
dl := get("?download=1")
if cd := dl.Header().Get("Content-Disposition"); !strings.HasPrefix(cd, "attachment") ||
!strings.Contains(cd, `filename="____.docx"`) { // ASCII 兜底:4 个汉字 → 4 个下划线
t.Errorf("attachment disposition mismatch: %s", cd)
}
}
// TestContentTypeByExtension:MIME 为空时按扩展名回退(docx 显式表 + csv 显式表),
// 未知扩展回落 text/plain,image kind 回落二进制流。
func TestContentTypeByExtension(t *testing.T) {
if ct := contentTypeOf(core.Artifact{Name: "a.DOCX"}); ct != "application/vnd.openxmlformats-officedocument.wordprocessingml.document" {
t.Errorf("docx ext (uppercase) mismatch: %s", ct)
}
if ct := contentTypeOf(core.Artifact{Name: "b.csv"}); !strings.HasPrefix(ct, "text/csv") {
t.Errorf("csv ext mismatch: %s", ct)
}
if ct := contentTypeOf(core.Artifact{Name: "c.bin", Kind: "image"}); ct != "application/octet-stream" {
t.Errorf("image fallback mismatch: %s", ct)
}
if ct := contentTypeOf(core.Artifact{Name: "d"}); !strings.HasPrefix(ct, "text/plain") {
t.Errorf("plain fallback mismatch: %s", ct)
}
if ct := contentTypeOf(core.Artifact{Name: "e.md", MIME: "text/x-override"}); ct != "text/x-override" {
t.Errorf("explicit mime must win: %s", ct)
}
}