mirror of
https://github.com/karust/openserp.git
synced 2026-08-05 16:53:54 +08:00
A rod panic during SearchImage propagated uncaught into fasthttp and killed the whole process: per-engine recover blocks only covered Search (5 of 6 engines had no recovery on SearchImage), and there was no Fiber recover middleware. Regression test: a panicking SearchImage returns 502 engine_internal and the server keeps serving.
72 lines
2.1 KiB
Go
72 lines
2.1 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// FP-1: panics from engine code must be converted to ErrEngineInternal by the
|
|
// central recovery in the resilient layer (invokeEngine) instead of killing
|
|
// the process. SearchImage had no per-engine recovery in 5 of 6 engines.
|
|
func TestInvokeEngineRecoversPanics(t *testing.T) {
|
|
engine := &engineMock{
|
|
name: "google",
|
|
initialized: true,
|
|
searchFn: func(_ context.Context, _ Query) ([]SearchResult, error) {
|
|
panic("rod: page crashed")
|
|
},
|
|
imageFn: func(_ context.Context, _ Query) ([]SearchResult, error) {
|
|
panic("rod: object not found")
|
|
},
|
|
}
|
|
|
|
for _, isImage := range []bool{false, true} {
|
|
results, err := invokeEngine(context.Background(), engine, Query{Text: "golang"}, isImage)
|
|
if results != nil {
|
|
t.Fatalf("isImage=%v: expected nil results after panic, got %v", isImage, results)
|
|
}
|
|
if !errors.Is(err, ErrEngineInternal) {
|
|
t.Fatalf("isImage=%v: expected ErrEngineInternal, got %v", isImage, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPanickingSearchImageReturns502AndServerSurvives(t *testing.T) {
|
|
engine := &engineMock{
|
|
name: "google",
|
|
initialized: true,
|
|
imageFn: func(_ context.Context, _ Query) ([]SearchResult, error) {
|
|
panic("rod: page crashed")
|
|
},
|
|
}
|
|
|
|
opts := DefaultServerOptions()
|
|
opts.Resilience.Retry.MaxRetries = 0
|
|
srv := NewServerWithOptions("127.0.0.1", 7130, opts, engine)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/google/image?text=golang", nil)
|
|
resp, err := srv.app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("image request failed: %v", err)
|
|
}
|
|
if resp.StatusCode != http.StatusBadGateway {
|
|
t.Fatalf("expected 502 for panicking SearchImage, got %d", resp.StatusCode)
|
|
}
|
|
var payload JSONErrorResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
|
t.Fatalf("decode error response: %v", err)
|
|
}
|
|
if payload.Error != "engine_internal" {
|
|
t.Fatalf("expected error=engine_internal, got %q", payload.Error)
|
|
}
|
|
|
|
second := request(t, srv, "/google/search?text=golang")
|
|
if second.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected server to keep serving after panic, got %d", second.StatusCode)
|
|
}
|
|
}
|