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.
65 lines
2.5 KiB
Go
65 lines
2.5 KiB
Go
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)
|
||
}
|
||
}
|