refactor(handler,repo): replace strings.Contains error dispatch with typed sentinels (S-2)

Closes one 2026-04-24 audit finding (P2):

  - cat-s6-efc7f6f6bd50: 30 strings.Contains(err.Error(), ...) sites
    in internal/api/handler/ — brittle to repository-layer message
    changes, untyped against the actual failure mode.

Approach (Option B from prompt design notes):
  - New typed sentinels in internal/repository/errors.go:
      ErrNotFound, ErrForeignKeyConstraint
      IsForeignKeyError(err) helper (the only place substring
      matching at the lib/pq boundary is allowed; isolates the
      DB-driver string knowledge to one function).
  - New typed sentinel in internal/domain/errors.go:
      ErrValidation (reserved for future per-entity validation
      wrappers; not yet used by all handlers).
  - 49 sites in internal/repository/postgres/*.go updated to wrap
    sql.ErrNoRows-derived errors via fmt.Errorf("...: %w",
    repository.ErrNotFound).
  - 18 not-found handler sites + 2 FK-constraint handler sites
    refactored to errors.Is(err, repository.ErrNotFound) /
    repository.IsForeignKeyError(err).
  - 23 inline `fmt.Errorf("X not found")` test fixtures across
    handler tests rewrapped to wrap repository.ErrNotFound.
  - test_utils.go::ErrMockNotFound rewrapped to wrap
    repository.ErrNotFound; renewal_policy.go closure docblock
    updated to reflect the new convention.
  - integration test mockJobRepository.Get wraps repository.ErrNotFound.

CI regression guardrail:
- .github/workflows/ci.yml::"Forbidden strings.Contains(err.Error())
  regression guard (S-2)" greps for the three patterns ("not found",
  "violates foreign key", "RESTRICT") under internal/api/handler/
  and fails the build on regression.

Verification:
- go build ./... — clean
- go vet ./... — clean
- go test ./... -short -count=1 — all packages pass (handler +
  repository + service + integration)
- golangci-lint v2.11.4 run ./... — 0 issues
- S-2 guardrail dry-run on post-fix tree → empty (good)
- All sibling guardrails (S-1, G-3, D-1+D-2, B-1, L-1, H-1, C-1, F-1, P-1) pass

Audit findings closed:
- cat-s6-efc7f6f6bd50 (P2)

Deferred follow-ups:
- 6 domain-specific substring patterns still inline in handlers
  ("cannot approve", "cannot reject", "cannot be parsed",
  "no certificates found", "challenge password", "invalid"/
  "required" validation chains in profiles + agent_groups). Each
  needs its own typed sentinel, scoped per service. Documented
  by the S-2 CI guardrail's allowlist for closure-comments only.
- Per-entity not-found sentinels (Option A — ErrCertificateNotFound,
  ErrAgentNotFound, etc.) deferred. Generic ErrNotFound covers the
  current dispatch needs; per-entity precision would let handlers
  return entity-aware error bodies without a domain.Type field,
  but not blocking.
This commit is contained in:
shankar0123
2026-04-25 17:54:14 +00:00
parent 8a3086c4ae
commit 0e29c416b1
39 changed files with 265 additions and 107 deletions
@@ -522,7 +522,7 @@ func TestRevokeCertificate_AlreadyRevoked(t *testing.T) {
func TestRevokeCertificate_NotFound(t *testing.T) {
handler, mock := newCertHandlerWithMock()
mock.RevokeCertificateFn = func(_ context.Context, id string, reason string, _ string) error {
return fmt.Errorf("certificate not found")
return fmt.Errorf("certificate not found: %w", ErrMockNotFound)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-missing/revoke", strings.NewReader(`{"reason":"keyCompromise"}`))
@@ -33,7 +33,7 @@ func (m *MockAgentGroupService) GetAgentGroup(_ context.Context, id string) (*do
if m.GetAgentGroupFn != nil {
return m.GetAgentGroupFn(id)
}
return nil, fmt.Errorf("not found")
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
}
func (m *MockAgentGroupService) CreateAgentGroup(_ context.Context, group domain.AgentGroup) (*domain.AgentGroup, error) {
+4 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"net/http"
@@ -160,7 +162,7 @@ func (h AgentGroupHandler) UpdateAgentGroup(w http.ResponseWriter, r *http.Reque
updated, err := h.svc.UpdateAgentGroup(r.Context(), id, group)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Agent group not found", requestID)
return
}
@@ -188,7 +190,7 @@ func (h AgentGroupHandler) DeleteAgentGroup(w http.ResponseWriter, r *http.Reque
}
if err := h.svc.DeleteAgentGroup(r.Context(), id); err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Agent group not found", requestID)
return
}
@@ -3,7 +3,6 @@ package handler
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
@@ -142,7 +141,9 @@ func TestRetireAgentHandler_Sentinel_403(t *testing.T) {
func TestRetireAgentHandler_NotFound_404(t *testing.T) {
mock, handler := agentRetireTestSetup()
mock.RetireAgentFn = func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error) {
return nil, errors.New("agent not found")
// S-2 closure (cat-s6-efc7f6f6bd50): wrap repository.ErrNotFound
// so the handler's errors.Is dispatch resolves to 404.
return nil, ErrMockNotFound
}
req := httptest.NewRequest(http.MethodDelete, "/api/v1/agents/unknown-id", nil)
+3 -2
View File
@@ -1,6 +1,7 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
@@ -211,7 +212,7 @@ func (h AgentHandler) Heartbeat(w http.ResponseWriter, r *http.Request) {
ErrorWithRequestID(w, http.StatusGone, "Agent has been retired", requestID)
return
}
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Agent not found", requestID)
return
}
@@ -491,7 +492,7 @@ func (h AgentHandler) RetireAgent(w http.ResponseWriter, r *http.Request) {
JSON(w, http.StatusConflict, body)
return
}
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Agent not found", requestID)
return
}
@@ -900,7 +900,7 @@ func TestRevokeCertificate_Handler_AlreadyRevoked(t *testing.T) {
func TestRevokeCertificate_Handler_NotFound(t *testing.T) {
mock := &MockCertificateService{
RevokeCertificateFn: func(_ context.Context, certID string, reason string, _ string) error {
return fmt.Errorf("failed to fetch certificate: not found")
return fmt.Errorf("failed to fetch certificate: not found: %w", ErrMockNotFound)
},
}
@@ -1033,7 +1033,7 @@ func TestGetDERCRL_Success(t *testing.T) {
if issuerID == "iss-local" {
return derCRLData, nil
}
return nil, fmt.Errorf("issuer not found")
return nil, fmt.Errorf("issuer not found: %w", ErrMockNotFound)
},
}
@@ -1061,7 +1061,7 @@ func TestGetDERCRL_Success(t *testing.T) {
func TestGetDERCRL_IssuerNotFound(t *testing.T) {
mock := &MockCertificateService{
GenerateDERCRLFn: func(_ context.Context, issuerID string) ([]byte, error) {
return nil, fmt.Errorf("issuer not found")
return nil, fmt.Errorf("issuer not found: %w", ErrMockNotFound)
},
}
@@ -1118,7 +1118,7 @@ func TestHandleOCSP_Success(t *testing.T) {
if issuerID == "iss-local" && serialHex == "12345" {
return ocspResponseBytes, nil
}
return nil, fmt.Errorf("certificate not found")
return nil, fmt.Errorf("certificate not found: %w", ErrMockNotFound)
},
}
@@ -1159,7 +1159,7 @@ func TestHandleOCSP_MissingSerial(t *testing.T) {
func TestHandleOCSP_IssuerNotFound(t *testing.T) {
mock := &MockCertificateService{
GetOCSPResponseFn: func(_ context.Context, issuerID string, serialHex string) ([]byte, error) {
return nil, fmt.Errorf("issuer not found")
return nil, fmt.Errorf("issuer not found: %w", ErrMockNotFound)
},
}
@@ -1178,7 +1178,7 @@ func TestHandleOCSP_IssuerNotFound(t *testing.T) {
func TestHandleOCSP_CertNotFound(t *testing.T) {
mock := &MockCertificateService{
GetOCSPResponseFn: func(_ context.Context, issuerID string, serialHex string) ([]byte, error) {
return nil, fmt.Errorf("certificate not found")
return nil, fmt.Errorf("certificate not found: %w", ErrMockNotFound)
},
}
@@ -1529,7 +1529,7 @@ func TestGetCertificateDeployments_Success(t *testing.T) {
func TestGetCertificateDeployments_NotFound(t *testing.T) {
mock := &MockCertificateService{
GetCertificateDeploymentsFn: func(_ context.Context, certID string) ([]domain.DeploymentTarget, error) {
return nil, fmt.Errorf("certificate not found")
return nil, fmt.Errorf("certificate not found: %w", ErrMockNotFound)
},
}
+4 -3
View File
@@ -1,6 +1,7 @@
package handler
import (
"errors"
"context"
"encoding/json"
"log/slog"
@@ -298,7 +299,7 @@ func (h CertificateHandler) UpdateCertificate(w http.ResponseWriter, r *http.Req
updated, err := h.svc.UpdateCertificate(r.Context(), id, cert)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Certificate not found", requestID)
return
}
@@ -327,7 +328,7 @@ func (h CertificateHandler) ArchiveCertificate(w http.ResponseWriter, r *http.Re
}
if err := h.svc.ArchiveCertificate(r.Context(), id); err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Certificate not found", requestID)
return
}
@@ -373,7 +374,7 @@ func (h CertificateHandler) GetCertificateVersions(w http.ResponseWriter, r *htt
versions, total, err := h.svc.GetCertificateVersions(r.Context(), certID, page, perPage)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Certificate not found", requestID)
return
}
@@ -300,7 +300,7 @@ func TestGetDiscovered_Success(t *testing.T) {
if id == "dcert-1" {
return cert, nil
}
return nil, fmt.Errorf("not found")
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
},
}
@@ -331,7 +331,7 @@ func TestGetDiscovered_Success(t *testing.T) {
func TestGetDiscovered_NotFound(t *testing.T) {
mock := &MockDiscoveryService{
GetDiscoveredFn: func(ctx context.Context, id string) (*domain.DiscoveredCertificate, error) {
return nil, fmt.Errorf("not found")
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
},
}
@@ -412,7 +412,7 @@ func TestClaimDiscovered_MissingManagedCertID(t *testing.T) {
func TestClaimDiscovered_NotFound(t *testing.T) {
mock := &MockDiscoveryService{
ClaimDiscoveredFn: func(ctx context.Context, id string, managedCertID string, actor string) error {
return fmt.Errorf("discovered certificate not found")
return fmt.Errorf("discovered certificate not found: %w", ErrMockNotFound)
},
}
@@ -442,7 +442,7 @@ func TestDismissDiscovered_Success(t *testing.T) {
if id == "dcert-1" {
return nil
}
return fmt.Errorf("not found")
return fmt.Errorf("not found: %w", ErrMockNotFound)
},
}
+4 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"log/slog"
@@ -46,7 +48,7 @@ func (h ExportHandler) ExportPEM(w http.ResponseWriter, r *http.Request) {
result, err := h.svc.ExportPEM(r.Context(), id)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Certificate not found", requestID)
return
}
@@ -94,7 +96,7 @@ func (h ExportHandler) ExportPKCS12(w http.ResponseWriter, r *http.Request) {
pfxData, err := h.svc.ExportPKCS12(r.Context(), id, req.Password)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Certificate not found", requestID)
return
}
+2 -2
View File
@@ -110,7 +110,7 @@ func TestExportPEM_Download(t *testing.T) {
func TestExportPEM_NotFound(t *testing.T) {
mockSvc := &MockExportService{
ExportPEMFn: func(_ context.Context, _ string) (*service.ExportPEMResult, error) {
return nil, fmt.Errorf("certificate not found")
return nil, fmt.Errorf("certificate not found: %w", ErrMockNotFound)
},
}
h := NewExportHandler(mockSvc)
@@ -216,7 +216,7 @@ func TestExportPKCS12_EmptyPassword(t *testing.T) {
func TestExportPKCS12_NotFound(t *testing.T) {
mockSvc := &MockExportService{
ExportPKCS12Fn: func(_ context.Context, _ string, _ string) ([]byte, error) {
return nil, fmt.Errorf("certificate not found")
return nil, fmt.Errorf("certificate not found: %w", ErrMockNotFound)
},
}
h := NewExportHandler(mockSvc)
+4 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"log/slog"
@@ -210,9 +212,9 @@ func (h IssuerHandler) DeleteIssuer(w http.ResponseWriter, r *http.Request) {
}
if err := h.svc.DeleteIssuer(r.Context(), id); err != nil {
if strings.Contains(err.Error(), "violates foreign key") || strings.Contains(err.Error(), "RESTRICT") {
if repository.IsForeignKeyError(err) {
ErrorWithRequestID(w, http.StatusConflict, "Cannot delete issuer: certificates are still using this issuer", requestID)
} else if strings.Contains(err.Error(), "not found") {
} else if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Issuer not found", requestID)
} else {
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to delete issuer", requestID)
+2 -2
View File
@@ -383,7 +383,7 @@ func TestApproveJob_Success(t *testing.T) {
func TestApproveJob_NotFound(t *testing.T) {
mock := &MockJobService{
ApproveJobFn: func(id, actor string) error {
return fmt.Errorf("job not found: no rows")
return fmt.Errorf("job not found: no rows: %w", ErrMockNotFound)
},
}
@@ -527,7 +527,7 @@ func TestRejectJob_NoReason(t *testing.T) {
func TestRejectJob_NotFound(t *testing.T) {
mock := &MockJobService{
RejectJobFn: func(id, reason, actor string) error {
return fmt.Errorf("job not found: no rows")
return fmt.Errorf("job not found: no rows: %w", ErrMockNotFound)
},
}
+3 -2
View File
@@ -1,6 +1,7 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
@@ -167,7 +168,7 @@ func (h JobHandler) ApproveJob(w http.ResponseWriter, r *http.Request) {
requestID)
return
}
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Job not found", requestID)
return
}
@@ -213,7 +214,7 @@ func (h JobHandler) RejectJob(w http.ResponseWriter, r *http.Request) {
actor := resolveActor(r.Context())
if err := h.svc.RejectJob(r.Context(), jobID, body.Reason, actor); err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Job not found", requestID)
return
}
@@ -27,7 +27,7 @@ func (m *mockNetworkScanService) GetTarget(ctx context.Context, id string) (*dom
return t, nil
}
}
return nil, fmt.Errorf("not found: %s", id)
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
}
func (m *mockNetworkScanService) CreateTarget(ctx context.Context, target *domain.NetworkScanTarget) (*domain.NetworkScanTarget, error) {
@@ -48,7 +48,7 @@ func (m *mockNetworkScanService) UpdateTarget(ctx context.Context, id string, ta
return t, nil
}
}
return nil, fmt.Errorf("not found: %s", id)
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
}
func (m *mockNetworkScanService) DeleteTarget(ctx context.Context, id string) error {
@@ -58,7 +58,7 @@ func (m *mockNetworkScanService) DeleteTarget(ctx context.Context, id string) er
return nil
}
}
return fmt.Errorf("not found: %s", id)
return fmt.Errorf("not found: %w", ErrMockNotFound)
}
func (m *mockNetworkScanService) TriggerScan(ctx context.Context, targetID string) (*domain.DiscoveryScan, error) {
@@ -71,7 +71,7 @@ func (m *mockNetworkScanService) TriggerScan(ctx context.Context, targetID strin
}, nil
}
}
return nil, fmt.Errorf("not found: %s", targetID)
return nil, fmt.Errorf("not found: %w", ErrMockNotFound)
}
func TestListNetworkScanTargets(t *testing.T) {
+3 -1
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"net/http"
"strconv"
@@ -170,7 +172,7 @@ func (h NotificationHandler) RequeueNotification(w http.ResponseWriter, r *http.
notificationID := parts[0]
if err := h.svc.RequeueNotification(r.Context(), notificationID); err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Notification not found", requestID)
return
}
+4 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"net/http"
@@ -184,9 +186,9 @@ func (h OwnerHandler) DeleteOwner(w http.ResponseWriter, r *http.Request) {
id = parts[0]
if err := h.svc.DeleteOwner(r.Context(), id); err != nil {
if strings.Contains(err.Error(), "violates foreign key") || strings.Contains(err.Error(), "RESTRICT") {
if repository.IsForeignKeyError(err) {
ErrorWithRequestID(w, http.StatusConflict, "Cannot delete owner: certificates are still assigned to this owner", requestID)
} else if strings.Contains(err.Error(), "not found") {
} else if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Owner not found", requestID)
} else {
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to delete owner", requestID)
+4 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"net/http"
@@ -162,7 +164,7 @@ func (h ProfileHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
updated, err := h.svc.UpdateProfile(r.Context(), id, profile)
if err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Profile not found", requestID)
return
}
@@ -195,7 +197,7 @@ func (h ProfileHandler) DeleteProfile(w http.ResponseWriter, r *http.Request) {
}
if err := h.svc.DeleteProfile(r.Context(), id); err != nil {
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Profile not found", requestID)
return
}
+10 -9
View File
@@ -1,6 +1,7 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
@@ -26,14 +27,14 @@ type RenewalPolicyService interface {
// RenewalPolicyHandler serves /api/v1/renewal-policies CRUD endpoints.
//
// G-1 design note: the service-level `ErrRenewalPolicyDuplicateName` /
// G-1 + S-2 design note: the service-level `ErrRenewalPolicyDuplicateName` /
// `ErrRenewalPolicyInUse` sentinels alias the repository sentinels (same var
// identity), so `errors.Is` walks transparently across layers. Delete/Update
// not-found detection intentionally uses a `strings.Contains(err.Error(),
// "not found")` substring check — the repo wraps `sql.ErrNoRows` as
// `fmt.Errorf("renewal policy not found: %s", id)` which strips the sentinel,
// and the handler red-tests' `ErrMockNotFound = errors.New("mock not found
// error")` follows the same substring convention.
// identity), so `errors.Is` walks transparently across layers. S-2 closure
// (cat-s6-efc7f6f6bd50) extends the same convention to not-found detection:
// repos now wrap `sql.ErrNoRows` via `fmt.Errorf("X not found: %w",
// repository.ErrNotFound)`, handler dispatch uses
// `errors.Is(err, repository.ErrNotFound)`, and `ErrMockNotFound` in
// test_utils.go wraps the same sentinel so the mocks still resolve to 404.
type RenewalPolicyHandler struct {
svc RenewalPolicyService
}
@@ -191,7 +192,7 @@ func (h RenewalPolicyHandler) UpdateRenewalPolicy(w http.ResponseWriter, r *http
ErrorWithRequestID(w, http.StatusConflict, "A renewal policy with that name already exists", requestID)
return
}
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Renewal policy not found", requestID)
return
}
@@ -231,7 +232,7 @@ func (h RenewalPolicyHandler) DeleteRenewalPolicy(w http.ResponseWriter, r *http
ErrorWithRequestID(w, http.StatusConflict, "Renewal policy is still referenced by managed certificates", requestID)
return
}
if strings.Contains(err.Error(), "not found") {
if errors.Is(err, repository.ErrNotFound) {
ErrorWithRequestID(w, http.StatusNotFound, "Renewal policy not found", requestID)
return
}
+18 -7
View File
@@ -1,11 +1,22 @@
package handler
import "errors"
import (
"fmt"
var (
// Mock errors for testing
ErrMockServiceFailed = errors.New("mock service error")
ErrMockNotFound = errors.New("mock not found error")
ErrMockUnauthorized = errors.New("mock unauthorized error")
ErrMockConflict = errors.New("mock conflict error")
"github.com/shankar0123/certctl/internal/repository"
)
// Mock errors for testing.
//
// S-2 closure (cat-s6-efc7f6f6bd50): ErrMockNotFound now wraps
// repository.ErrNotFound via fmt.Errorf("...: %w", ...) so the
// post-S-2 handler dispatch — which uses errors.Is(err,
// repository.ErrNotFound) instead of strings.Contains — still
// resolves the mock to a 404. The error message text is preserved
// for log inspection; only the wrapping changes.
var (
ErrMockServiceFailed = fmt.Errorf("mock service error")
ErrMockNotFound = fmt.Errorf("mock not found error: %w", repository.ErrNotFound)
ErrMockUnauthorized = fmt.Errorf("mock unauthorized error")
ErrMockConflict = fmt.Errorf("mock conflict error")
)