package linkmeta import ( "context" "fmt" "net" "net/http" "net/http/httptest" "net/url" "testing" "golang.org/x/text/encoding/simplifiedchinese" ) // gbkPage 把 UTF-8 字符串编成 GBK——老派中文站就是这么发的。 func gbkPage(t *testing.T, s string) []byte { t.Helper() out, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(s)) if err != nil { t.Fatal(err) } return out } func TestDecodeGBKMetaCharset(t *testing.T) { // 只在 里声明,HTTP 头不带 charset——这是最常见的写法 doc := `` raw := gbkPage(t, doc) got := parse(decode(raw, "text/html"), mustBase(t, "https://example.com/")) if got.Title != "围棋职业棋士的直播间" { t.Fatalf("GBK 未正确解码: %q", got.Title) } } func TestDecodeHTTPHeaderWins(t *testing.T) { doc := `标题测试` raw := gbkPage(t, doc) got := parse(decode(raw, `text/html; charset=GB2312`), mustBase(t, "https://example.com/")) if got.Title != "标题测试" { t.Fatalf("HTTP 头声明的字符集未生效: %q", got.Title) } } func TestDecodeHTTPEquivMeta(t *testing.T) { doc := `中文标题` got := parse(decode(gbkPage(t, doc), "text/html"), mustBase(t, "https://example.com/")) if got.Title != "中文标题" { t.Fatalf("http-equiv 形式未识别: %q", got.Title) } } func TestDecodePassthroughAndUnknown(t *testing.T) { utf8 := `已经是 UTF-8` if got := decode([]byte(utf8), "text/html; charset=utf-8"); got != utf8 { t.Error("UTF-8 不该走转换管线") } if got := decode([]byte(utf8), "text/html; charset=bogus-9999"); got != utf8 { t.Error("认不出的字符集应退回原始字节而不是报错") } if got := decode([]byte("\ufeff"+utf8), "text/html"); got != utf8 { t.Error("BOM 应被剥掉") } } func TestCharsetFromHeader(t *testing.T) { cases := map[string]string{ `text/html; charset=UTF-8`: "utf-8", `text/html;charset="gbk"`: "gbk", `text/html; charset=GB2312`: "gbk", // 别名归一:GBK 是超集 `text/html`: "", } for in, want := range cases { if got := charsetFromHeader(in); got != want { t.Errorf("charsetFromHeader(%q) = %q, want %q", in, got, want) } } } func TestCharsetFromMeta(t *testing.T) { cases := map[string]string{ ``: "big5", ``: "gbk", ``: "shift_jis", `没有声明`: "", } for in, want := range cases { if got := charsetFromMeta([]byte(in)); got != want { t.Errorf("charsetFromMeta(%q) = %q, want %q", in, got, want) } } } // 端到端:真发一个 GBK 页面(HTTP 头故意不写 charset),确认整条链路出正确卡片。 func TestFetcherDecodesGBKEndToEnd(t *testing.T) { doc := `` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") // 不声明 charset,逼它去嗅 meta fmt.Fprint(w, string(gbkPage(t, doc))) })) defer srv.Close() f := &Fetcher{Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { var d net.Dialer return d.DialContext(ctx, network, addr) }} c, err := f.Fetch(context.Background(), srv.URL) if err != nil { t.Fatal(err) } if c.Title != "鱼妹妹下棋" || c.Site != "某中文站" { t.Fatalf("card=%+v", c) } } func mustBase(t *testing.T, s string) *url.URL { t.Helper() u, err := url.Parse(s) if err != nil { t.Fatal(err) } return u }