package matching import ( "errors" "net/http/httptest" "testing" ) // THE GATE MUST ANSWER 422 AND SAY WHY. // // A refusal that arrives as a 500 with "internal error" is indistinguishable from a broken server, and the // person meeting it is a recruiter who has done nothing wrong and can fix it in ten seconds if told how. // This pins the mapping so a future error added to the switch cannot quietly demote it. func TestNoticeRequiredIsAnUnprocessableEntityWithTheReason(t *testing.T) { w := httptest.NewRecorder() writeServiceError(w, ErrNoticeRequired) if w.Code != 422 { t.Fatalf("status = %d, want 422. A rule the customer switched on is not a server fault.", w.Code) } body := w.Body.String() for _, want := range []string{"notice", "Record the notice"} { if !contains(body, want) { t.Errorf("the response does not contain %q, so the recruiter is not told what to do: %s", want, body) } } } // A wrapped sentinel must still map, because the gate returns it from inside a transaction closure and the // error travels wrapped through RunInTenantTx. func TestWrappedNoticeErrorStillMaps(t *testing.T) { w := httptest.NewRecorder() writeServiceError(w, errors.Join(errors.New("tenant tx"), ErrNoticeRequired)) if w.Code != 422 { t.Fatalf("a wrapped sentinel mapped to %d, want 422", w.Code) } } func contains(hay, needle string) bool { return len(hay) >= len(needle) && (len(needle) == 0 || indexOf(hay, needle) >= 0) } func indexOf(hay, needle string) int { for i := 0; i+len(needle) <= len(hay); i++ { if hay[i:i+len(needle)] == needle { return i } } return -1 }