119 lines
3.9 KiB
Go
119 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
type fakeQQ struct{}
|
|
|
|
func (fakeQQ) Search(keyword string, limit int) ([]song, error) {
|
|
return []song{{Name: keyword, MID: "0004jPDk2eB2dt", Singer: "Tester", Interval: limit}}, nil
|
|
}
|
|
func (fakeQQ) Lyrics(string) (lyricResult, error) {
|
|
return lyricResult{Code: 0, Lyric: "[00:01.00]line", Trans: "[00:01.00]翻译", Roma: "[00:01.00]sound"}, nil
|
|
}
|
|
|
|
func testApp() (*localApp, http.Handler, fs.FS) {
|
|
web := fstest.MapFS{
|
|
"app.html": &fstest.MapFile{Data: []byte("app")},
|
|
"app.js": &fstest.MapFile{Data: []byte("js")},
|
|
"styles.css": &fstest.MapFile{Data: []byte("css")},
|
|
}
|
|
app := newApp("http://127.0.0.1:18765", "test-secret", web, fakeQQ{})
|
|
return app, app.routes(), web
|
|
}
|
|
|
|
func request(method, target, body string) *http.Request {
|
|
r := httptest.NewRequest(method, target, strings.NewReader(body))
|
|
r.Host = "127.0.0.1:18765"
|
|
return r
|
|
}
|
|
|
|
func validSessionCookie() *http.Cookie {
|
|
return &http.Cookie{Name: sessionCookie, Value: "test-secret"}
|
|
}
|
|
|
|
func TestLaunchTokenCreatesStrictSession(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, request(http.MethodGet, "http://127.0.0.1:18765/start?t=test-secret", ""))
|
|
if w.Code != http.StatusSeeOther {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
cookies := w.Result().Cookies()
|
|
if len(cookies) != 1 || !cookies[0].HttpOnly || cookies[0].SameSite != http.SameSiteStrictMode {
|
|
t.Fatalf("unsafe cookie: %#v", cookies)
|
|
}
|
|
}
|
|
|
|
func TestRejectsWrongHost(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
r := request(http.MethodGet, "http://127.0.0.1:18765/health", "")
|
|
r.Host = "attacker.example"
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, r)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestRejectsCrossOriginAPI(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
r := request(http.MethodPost, "http://127.0.0.1:18765/api/search", `{"keyword":"test","limit":10}`)
|
|
r.AddCookie(validSessionCookie())
|
|
r.Header.Set("Content-Type", "application/json")
|
|
r.Header.Set("Origin", "https://attacker.example")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, r)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestValidSearchAndSecurityHeaders(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
r := request(http.MethodPost, "http://127.0.0.1:18765/api/search", `{"keyword":"测试","limit":10}`)
|
|
r.AddCookie(validSessionCookie())
|
|
r.Header.Set("Content-Type", "application/json")
|
|
r.Header.Set("Origin", "http://127.0.0.1:18765")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, r)
|
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "测试") {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Header().Get("Content-Security-Policy"), "default-src 'none'") {
|
|
t.Fatal("strict CSP is missing")
|
|
}
|
|
}
|
|
|
|
func TestRejectsInvalidMID(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
r := request(http.MethodPost, "http://127.0.0.1:18765/api/lyrics", `{"mid":"https://attacker.example/"}`)
|
|
r.AddCookie(validSessionCookie())
|
|
r.Header.Set("Content-Type", "application/json")
|
|
r.Header.Set("Origin", "http://127.0.0.1:18765")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, r)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestLyricsReturnsAvailableTranslationAndPhonetics(t *testing.T) {
|
|
_, handler, _ := testApp()
|
|
r := request(http.MethodPost, "http://127.0.0.1:18765/api/lyrics", `{"mid":"0004jPDk2eB2dt"}`)
|
|
r.AddCookie(validSessionCookie())
|
|
r.Header.Set("Content-Type", "application/json")
|
|
r.Header.Set("Origin", "http://127.0.0.1:18765")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, r)
|
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `"trans":"[00:01.00]翻译"`) || !strings.Contains(w.Body.String(), `"roma":"[00:01.00]sound"`) {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
}
|