package testkit import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "apps/backend/internal/config" "apps/backend/internal/domain" "apps/backend/internal/handler" "apps/backend/internal/response" "apps/backend/internal/svc" "github.com/stretchr/testify/require" "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/rest" ) // FixedNow is a frozen clock for schedule tests (M4+). var FixedNow = time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC) // TestConfig returns a minimal in-memory config for tests. func TestConfig() config.Config { var c config.Config c.Name = "haixun-test" c.Host = "127.0.0.1" c.Port = 0 c.Timeout = 3000 c.Log.Mode = "console" c.Log.Level = "error" c.Mongo.URI = "mongodb://127.0.0.1:27017" c.Mongo.Database = "haixun_test" c.CacheRedis = cache.CacheConf{{ RedisConf: redis.RedisConf{Host: "127.0.0.1:6379", Type: "node"}, Weight: 100, }} c.Auth.AccessSecret = "test-access" c.Auth.RefreshSecret = "test-refresh" c.Auth.AccessExpire = 3600 c.Auth.RefreshExpire = 86400 c.Worker.ID = "test-worker" c.Worker.PollIntervalMs = 100 return c } // StartGateway builds a rest.Server with registered routes (not listening). // Callers use ServeHTTP via httptest for unit/integration without bind. func StartGateway(t *testing.T) (*rest.Server, *svc.ServiceContext) { t.Helper() c := TestConfig() // Avoid log spam / file mode issues in tests server := rest.MustNewServer(c.RestConf) ctx := svc.NewServiceContext(c) handler.RegisterHandlers(server, ctx) t.Cleanup(func() { server.Stop() }) return server, ctx } // AssertEnvelope102000 checks success envelope shape (A-07). func AssertEnvelope102000(t *testing.T, body []byte) response.Envelope { t.Helper() var env response.Envelope require.NoError(t, json.Unmarshal(body, &env)) require.Equal(t, domain.SuccessCode, env.Code, "expected success code 102000") require.NotEmpty(t, env.Message) require.Nil(t, env.Error) return env } // AssertFailNotEmptySuccess ensures response is not 102000 with empty success (X-01). func AssertFailNotEmptySuccess(t *testing.T, body []byte) response.Envelope { t.Helper() var env response.Envelope require.NoError(t, json.Unmarshal(body, &env)) require.NotEqual(t, domain.SuccessCode, env.Code, "must not be success empty") require.NotEmpty(t, env.Message) return env } // GetJSON issues GET against the in-process server mux via handler registration. // go-zero rest.Server does not expose ServeHTTP easily in all versions; we re-register // on a plain mux for tests. func NewTestMux(svcCtx *svc.ServiceContext) http.Handler { mux := http.NewServeMux() // Mirror routes.go paths for testkit convenience h := handlerProbe{svc: svcCtx} mux.HandleFunc("GET /api/v1/ping", h.ping) mux.HandleFunc("GET /api/v1/health", h.ping) mux.HandleFunc("GET /api/v1/_not_implemented_probe", h.notImpl) return mux } type handlerProbe struct { svc *svc.ServiceContext } func (h handlerProbe) ping(w http.ResponseWriter, r *http.Request) { // Import cycle avoided: call response directly with same payload as ping logic response.OK(w, map[string]string{"status": "ok", "service": "haixun-gateway"}) } func (h handlerProbe) notImpl(w http.ResponseWriter, r *http.Request) { response.NotImplemented(w, "probe") } // DoGet helper func DoGet(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodGet, path, nil) rr := httptest.NewRecorder() h.ServeHTTP(rr, req) return rr }