fix(api): distinguish domain-not-found (404) from provider failure (502) on zone endpoints
Introduce service.ErrProviderUnavailable, wrapped only around the provider GetRecords call in ZoneRecords. handleZoneRecords and handleTemplateFromZone now use errors.Is against it to tell a real provider outage (502) apart from local resolution failures such as an unknown domain (404), instead of collapsing every ZoneRecords error into a blanket 502. Also fixes handleTemplateFromZone's GetDomain error branch to return 404 "domain not found" instead of 500, for consistency with handleSetDomainTemplate/handleDomainHistory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/diff"
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/model"
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/provider"
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/service"
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/store"
|
||||
"github.com/vasyakrg/dns-autoresolver/internal/store/dto"
|
||||
)
|
||||
@@ -688,13 +690,13 @@ func TestTemplateFromZone_SnapshotsManagedRecordsOnlyAndAttaches(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestZoneRecords_ProviderErrorReturns502 covers the provider-failure path:
|
||||
// a GetRecords error from the provider must surface as 502 (bad gateway),
|
||||
// not a generic 500 or a hung response.
|
||||
// an error wrapping service.ErrProviderUnavailable (i.e. GetRecords itself
|
||||
// failed) must surface as 502 (bad gateway), not a generic 500 or 404.
|
||||
func TestZoneRecords_ProviderErrorReturns502(t *testing.T) {
|
||||
a, ts := newTenantTestAPI()
|
||||
domID := uuid.New()
|
||||
ts.domains = []store.Domain{{ID: domID, ZoneName: "example.com", ZoneID: "z1"}}
|
||||
a.Svc = &mockCheckApplier{zoneErr: errors.New("provider unreachable")}
|
||||
a.Svc = &mockCheckApplier{zoneErr: fmt.Errorf("%w: boom", service.ErrProviderUnavailable)}
|
||||
router := NewRouter(a)
|
||||
|
||||
req := requestWithSessionCookie(http.MethodGet, "/api/v1/projects/"+testPID+"/domains/"+domID.String()+"/records", nil)
|
||||
@@ -705,3 +707,23 @@ func TestZoneRecords_ProviderErrorReturns502(t *testing.T) {
|
||||
t.Fatalf("expected 502, got %d body %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestZoneRecords_NotFoundReturns404 covers the domain-resolution-failure
|
||||
// path: an error that does NOT wrap service.ErrProviderUnavailable (e.g. the
|
||||
// domain doesn't exist in this project) must surface as 404, not 502 — the
|
||||
// provider was never even reached.
|
||||
func TestZoneRecords_NotFoundReturns404(t *testing.T) {
|
||||
a, ts := newTenantTestAPI()
|
||||
domID := uuid.New()
|
||||
ts.domains = []store.Domain{{ID: domID, ZoneName: "example.com", ZoneID: "z1"}}
|
||||
a.Svc = &mockCheckApplier{zoneErr: errors.New("not found")}
|
||||
router := NewRouter(a)
|
||||
|
||||
req := requestWithSessionCookie(http.MethodGet, "/api/v1/projects/"+testPID+"/domains/"+domID.String()+"/records", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d body %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user