package localserver import ( "bytes" "encoding/json" "errors" "net/http" "testing" "time" "everypublish/shared/proto" ) type fakeState struct { online bool chs []proto.Challenge err error } func (f *fakeState) Online() bool { return f.online } func (f *fakeState) SessionID() string { return "s1" } func (f *fakeState) Challenges() []proto.Challenge { return f.chs } func (f *fakeState) SolveChallenge(id, value string) error { return f.err } func startTestServer(t *testing.T, state CoreState, pair PairFunc) (*Server, string) { t.Helper() token := RandToken() s := New(state, pair, token, t.TempDir(), nil) go func() { _ = s.Listen() }() for i := 0; i < 50; i++ { if s.Addr() != "" { break } time.Sleep(10 * time.Millisecond) } if s.Addr() == "" { t.Fatal("server did not start") } return s, token } func get(t *testing.T, base, path, token string) (int, map[string]interface{}) { t.Helper() req, _ := http.NewRequest("GET", "http://"+base+path, nil) if token != "" { req.Header.Set("X-Agent-Token", token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("get: %v", err) } defer resp.Body.Close() var m map[string]interface{} _ = json.NewDecoder(resp.Body).Decode(&m) return resp.StatusCode, m } func TestStatusAuth(t *testing.T) { s, token := startTestServer(t, &fakeState{online: true}, nil) code, _ := get(t, s.Addr(), "/status", "") if code != 401 { t.Fatalf("no token should be 401, got %d", code) } code, m := get(t, s.Addr(), "/status", token) if code != 200 || m["online"] != true { t.Fatalf("status wrong: %d %v", code, m) } } func TestPairCallback(t *testing.T) { var gotCode, gotName string s, token := startTestServer(t, &fakeState{}, func(code, name string) error { gotCode, gotName = code, name return nil }) // 未带 token → 401 req, _ := http.NewRequest("POST", "http://"+s.Addr()+"/pair", bytes.NewBufferString(`{"code":"AB12CD","name":"PC"}`)) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("pair: %v", err) } if resp.StatusCode != 401 { t.Fatalf("pair without token should 401, got %d", resp.StatusCode) } resp.Body.Close() // 带 token → 回调被调用 req2, _ := http.NewRequest("POST", "http://"+s.Addr()+"/pair", bytes.NewBufferString(`{"code":"AB12CD","name":"PC"}`)) req2.Header.Set("X-Agent-Token", token) resp2, err := http.DefaultClient.Do(req2) if err != nil { t.Fatalf("pair: %v", err) } resp2.Body.Close() if gotCode != "AB12CD" || gotName != "PC" { t.Fatalf("callback not invoked: %q %q", gotCode, gotName) } } func TestPairErrorPropagates(t *testing.T) { s, token := startTestServer(t, &fakeState{}, func(code, name string) error { return errors.New("配对码无效") }) req, _ := http.NewRequest("POST", "http://"+s.Addr()+"/pair", bytes.NewBufferString(`{"code":"X"}`)) req.Header.Set("X-Agent-Token", token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("pair: %v", err) } defer resp.Body.Close() var m map[string]interface{} _ = json.NewDecoder(resp.Body).Decode(&m) if m["code"] != float64(1) { t.Fatalf("expect error code 1, got %v", m) } } func TestSolve(t *testing.T) { s, token := startTestServer(t, &fakeState{err: nil}, nil) code, m := get(t, s.Addr(), "/challenges", token) if code != 200 { t.Fatalf("challenges: %d", code) } _ = m req, _ := http.NewRequest("POST", "http://"+s.Addr()+"/challenges/9", bytes.NewBufferString(`{"value":"123456"}`)) req.Header.Set("X-Agent-Token", token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("solve: %v", err) } defer resp.Body.Close() var m2 map[string]interface{} _ = json.NewDecoder(resp.Body).Decode(&m2) if m2["code"] != float64(0) { t.Fatalf("solve should succeed, got %v", m2) } }