package main import ( "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" ) type fakeReader struct { status ReaderStatus readResult DLLResult readErr error readPanic any hmacResult DLLResult hmacErr error lastType string lastNoPIN bool } func (f *fakeReader) Status() ReaderStatus { return f.status } func (f *fakeReader) ReadCard(readerType string, noPIN bool) (DLLResult, error) { f.lastType, f.lastNoPIN = readerType, noPIN if f.readPanic != nil { panic(f.readPanic) } return f.readResult, f.readErr } func (f *fakeReader) HMACSM3(_, _, _, _ string) (DLLResult, error) { return f.hmacResult, f.hmacErr } func testHandler(reader *fakeReader) http.Handler { cfg := defaultConfig(".") return newHandler(cfg, reader, discardLogger()) } func TestHealth(t *testing.T) { reader := &fakeReader{status: ReaderStatus{Loaded: true, DLLDir: `C:\\dll`, Arch: "386", Platform: "windows"}} recorder := httptest.NewRecorder() testHandler(reader).ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/health", nil)) if recorder.Code != http.StatusOK { t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String()) } var response apiResponse if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { t.Fatal(err) } if response.Code != 0 { t.Fatalf("unexpected response: %#v", response) } } func TestReadNoPINRequiresReaderType(t *testing.T) { recorder := httptest.NewRecorder() testHandler(&fakeReader{}).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{}`))) if recorder.Code != http.StatusBadRequest { t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String()) } } func TestReadNoPINSuccess(t *testing.T) { reader := &fakeReader{readResult: DLLResult{Code: 0, Success: true, Data: map[string]any{"Success": true, "Name": "测试"}}} recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{"reader_type":" ZJ_HZ_310000 "}`)) testHandler(reader).ServeHTTP(recorder, request) if recorder.Code != http.StatusOK || reader.lastType != "ZJ_HZ_310000" || !reader.lastNoPIN { t.Fatalf("status=%d type=%q noPIN=%v body=%s", recorder.Code, reader.lastType, reader.lastNoPIN, recorder.Body.String()) } } func TestReadFailure(t *testing.T) { reader := &fakeReader{readResult: DLLResult{Code: -1, Success: false, Raw: `{"Success":false,"Message":"未检测到卡"}`, Data: map[string]any{"Success": false, "Message": "未检测到卡"}}} recorder := httptest.NewRecorder() testHandler(reader).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{"reader_type":"ZJ_HZ_310000"}`))) if recorder.Code != http.StatusInternalServerError || !strings.Contains(recorder.Body.String(), "未检测到卡") { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } } func TestReadError(t *testing.T) { reader := &fakeReader{readErr: errors.New("DLL unavailable")} recorder := httptest.NewRecorder() testHandler(reader).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{"reader_type":"ZJ_HZ_310000"}`))) if recorder.Code != http.StatusInternalServerError { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } } func TestReadPanicIsRecovered(t *testing.T) { reader := &fakeReader{readPanic: "driver panic"} recorder := httptest.NewRecorder() testHandler(reader).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{"reader_type":"ZJ_HZ_310000"}`))) if recorder.Code != http.StatusInternalServerError || !strings.Contains(recorder.Body.String(), "服务内部异常") { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } } func TestWithPINRejectsOtherCity(t *testing.T) { recorder := httptest.NewRecorder() testHandler(&fakeReader{}).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read", strings.NewReader(`{"reader_type":"ZJ_HZ_310000"}`))) if recorder.Code != http.StatusBadRequest { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } } func TestHMACSuccess(t *testing.T) { reader := &fakeReader{hmacResult: DLLResult{Code: 0, Success: true, Data: "123:ABC"}} recorder := httptest.NewRecorder() testHandler(reader).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/hmac-sm3", strings.NewReader(`{"key":"k","secret":"1234","request_body":"{}"}`))) if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "123:ABC") { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } } func TestInvalidJSON(t *testing.T) { recorder := httptest.NewRecorder() testHandler(&fakeReader{}).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/card/read-nopin", strings.NewReader(`{`))) if recorder.Code != http.StatusBadRequest { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } }