diff --git a/backend/service/handler_test.go b/backend/service/handler_test.go index 718476e..d8647de 100644 --- a/backend/service/handler_test.go +++ b/backend/service/handler_test.go @@ -15,3 +15,15 @@ func TestHandlerReturnsServiceUnavailableWhenRuntimeIsNil(t *testing.T) { t.Fatalf("空运行时状态码错误:%d", response.Code) } } + +func TestHandlerReturnsServiceUnavailableForZeroRuntime(t *testing.T) { + runtime := &Runtime{} + response := httptest.NewRecorder() + runtime.Handler().ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/healthz", nil)) + if response.Code != http.StatusServiceUnavailable { + t.Fatalf("未打开的运行时应返回服务不可用:%d", response.Code) + } + if err := runtime.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/backend/service/runtime.go b/backend/service/runtime.go index 2985cb8..21dbee4 100644 --- a/backend/service/runtime.go +++ b/backend/service/runtime.go @@ -93,7 +93,7 @@ func Open(configPath, baseDir string) (*Runtime, error) { func (r *Runtime) Handler() http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - if r == nil { + if r == nil || r.ctx == nil { http.Error(writer, "Scert 核心服务未初始化", http.StatusServiceUnavailable) return } diff --git a/backend/service/runtime_transition_test.go b/backend/service/runtime_transition_test.go index b038ab8..f3c5abc 100644 --- a/backend/service/runtime_transition_test.go +++ b/backend/service/runtime_transition_test.go @@ -1,6 +1,7 @@ package service import ( + "context" "net/http" "net/http/httptest" "os" @@ -8,6 +9,7 @@ import ( "strings" "sync" "testing" + "time" "git.pcux.cn/xuans/Scert/backend/internal/setup" ) @@ -123,3 +125,42 @@ func TestDesktopTransitionRejectsCorruptedSavedConfig(t *testing.T) { t.Fatal("损坏配置不得回退到新的初始化或启动正式服务") } } + +func TestDesktopCloseCancelsInFlightInitialization(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + started := make(chan struct{}) + finished := make(chan struct{}) + runtime := &Runtime{ + ctx: ctx, cancel: cancel, configPath: filepath.Join(t.TempDir(), "scert.json"), + initial: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + close(started) + <-request.Context().Done() + writer.WriteHeader(http.StatusServiceUnavailable) + }), + } + go func() { + defer close(finished) + runtime.Handler().ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/api/v1/setup", nil)) + }() + select { + case <-started: + case <-time.After(5 * time.Second): + t.Fatal("初始化处理器没有启动") + } + closed := make(chan error, 1) + go func() { closed <- runtime.Close() }() + select { + case err := <-closed: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("关闭运行时没有取消在途初始化请求") + } + select { + case <-finished: + case <-time.After(5 * time.Second): + t.Fatal("关闭后仍有初始化处理器运行") + } +}