feat: launch privacy-first local lyrics tool
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
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"}, 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user