mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 23:31:39 +00:00
5c01c7f21f
C-001 — CreateCertificate was server-accepted with null owner_id,
team_id, renewal_policy_id because the GUI neither collected the fields
nor enforced them, even though the backend's ManagedCertificate schema
and handler contract treat them as required. Fix the contract at all
four layers:
- web/src/pages/CertificatesPage.tsx: replace owner_id/team_id free-
text inputs with <select> elements fed by getOwners/getTeams/
getPolicies queries; mark all three required; gate the Create
button on owner_id + team_id + renewal_policy_id being set.
- internal/api/handler/certificates.go: ValidateRequired for
owner_id, team_id, renewal_policy_id on CreateCertificate so the
handler returns HTTP 400 with the offending field name before the
service layer is reached.
- internal/mcp/types.go: drop ',omitempty' from
CreateCertificateInput.RenewalPolicyID so the MCP schema reflects
the required contract; Update inputs keep partial-update semantics.
- api/openapi.yaml: 'required: [name, common_name, renewal_policy_id,
issuer_id, owner_id, team_id]' was already present on the Create
schema; clarified DeploymentTarget.agent_id description to note the
FK contract.
C-002 — CreateTargetWizard accepted an empty or bogus agent_id and the
service inserted directly, producing a Postgres 23503 FK-violation that
bubbled out as a generic HTTP 500. The FK itself (migration 000001 line
104: agent_id TEXT NOT NULL REFERENCES agents(id)) is correct; we keep
the schema strict and add validation at three layers:
- internal/service/target.go: introduce
ErrAgentNotFound sentinel and pre-validate agent_id in
TargetService.CreateTarget — empty string returns
'agent_id is required'; a nonexistent id returns the full
'referenced agent does not exist: <id>' error. Both wrap
ErrAgentNotFound via fmt.Errorf %w so callers can use errors.Is.
- internal/api/handler/targets.go: ValidateRequired on agent_id; map
errors.Is(err, service.ErrAgentNotFound) to HTTP 400 instead of
letting it fall through to the generic 500 branch.
- internal/mcp/types.go: drop ',omitempty' from
CreateTargetInput.AgentID to match the required contract.
- web/src/pages/TargetsPage.tsx: replace the free-text Agent ID input
with a <select> populated from getAgents(); include agent in the
canProceedToReview gate so Next is disabled until an agent is
chosen.
Regression coverage (21 new subtests total):
- TestCreateCertificate_MissingRequiredField_Returns400 — 6 subtests,
one per required field, each proves the handler guard fires before
the mock service is called.
- TestCreateTarget_MissingAgentID_Returns400 — handler guard.
- TestCreateTarget_NonexistentAgent_Returns400 — pins the
ErrAgentNotFound -> 400 translation.
- TestTargetService_CreateTarget_MissingAgentID — errors.Is sentinel.
- TestTargetService_CreateTarget_NonexistentAgentID — errors.Is.
- The existing TestTargetService_CreateTarget_Success, along with
TestCreateTarget_{MissingName,MissingType,NameTooLong}_* handler
tests, were updated to seed a real agent or include agent_id in
the request body so the happy paths still run cleanly.
Gates (Phase 4):
- go build/vet/test/race: green
- go test -cover: internal/service 68.7% (gate 55%),
internal/api/handler 78.9% (gate 60%)
- golangci-lint on service+handler+mcp: 0 issues
- govulncheck: no reachable vulns
- tsc --noEmit: clean
- vitest: 223/223 passing
See cowork/certctl-coverage-gap-audit.md entries C-001 and C-002.
561 lines
16 KiB
Go
561 lines
16 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
"github.com/shankar0123/certctl/internal/service"
|
|
)
|
|
|
|
// MockTargetService is a mock implementation of TargetService interface.
|
|
type MockTargetService struct {
|
|
ListTargetsFn func(ctx context.Context, page, perPage int) ([]domain.DeploymentTarget, int64, error)
|
|
GetTargetFn func(ctx context.Context, id string) (*domain.DeploymentTarget, error)
|
|
CreateTargetFn func(ctx context.Context, target domain.DeploymentTarget) (*domain.DeploymentTarget, error)
|
|
UpdateTargetFn func(ctx context.Context, id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error)
|
|
DeleteTargetFn func(ctx context.Context, id string) error
|
|
TestConnectionFn func(ctx context.Context, id string) error
|
|
}
|
|
|
|
func (m *MockTargetService) ListTargets(ctx context.Context, page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
|
if m.ListTargetsFn != nil {
|
|
return m.ListTargetsFn(ctx, page, perPage)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (m *MockTargetService) GetTarget(ctx context.Context, id string) (*domain.DeploymentTarget, error) {
|
|
if m.GetTargetFn != nil {
|
|
return m.GetTargetFn(ctx, id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockTargetService) CreateTarget(ctx context.Context, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
if m.CreateTargetFn != nil {
|
|
return m.CreateTargetFn(ctx, target)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockTargetService) UpdateTarget(ctx context.Context, id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
if m.UpdateTargetFn != nil {
|
|
return m.UpdateTargetFn(ctx, id, target)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockTargetService) DeleteTarget(ctx context.Context, id string) error {
|
|
if m.DeleteTargetFn != nil {
|
|
return m.DeleteTargetFn(ctx, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MockTargetService) TestConnection(ctx context.Context, id string) error {
|
|
if m.TestConnectionFn != nil {
|
|
return m.TestConnectionFn(ctx, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestListTargets_Success(t *testing.T) {
|
|
now := time.Now()
|
|
t1 := domain.DeploymentTarget{
|
|
ID: "t-nginx-01",
|
|
Name: "NGINX Proxy",
|
|
Type: "nginx",
|
|
AgentID: "agent-001",
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
t2 := domain.DeploymentTarget{
|
|
ID: "t-f5-01",
|
|
Name: "F5 LTM",
|
|
Type: "f5",
|
|
AgentID: "agent-002",
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
mock := &MockTargetService{
|
|
ListTargetsFn: func(_ context.Context, page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
|
return []domain.DeploymentTarget{t1, t2}, 2, nil
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListTargets(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp PagedResponse
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp.Total != 2 {
|
|
t.Errorf("expected total 2, got %d", resp.Total)
|
|
}
|
|
}
|
|
|
|
func TestListTargets_Pagination(t *testing.T) {
|
|
var capturedPage, capturedPerPage int
|
|
mock := &MockTargetService{
|
|
ListTargetsFn: func(_ context.Context, page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
|
capturedPage = page
|
|
capturedPerPage = perPage
|
|
return []domain.DeploymentTarget{}, 0, nil
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets?page=4&per_page=5", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListTargets(w, req)
|
|
|
|
if capturedPage != 4 {
|
|
t.Errorf("expected page 4, got %d", capturedPage)
|
|
}
|
|
if capturedPerPage != 5 {
|
|
t.Errorf("expected per_page 5, got %d", capturedPerPage)
|
|
}
|
|
}
|
|
|
|
func TestListTargets_ServiceError(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
ListTargetsFn: func(_ context.Context, page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
|
return nil, 0, ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListTargets(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected status 500, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListTargets_MethodNotAllowed(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListTargets(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetTarget_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockTargetService{
|
|
GetTargetFn: func(_ context.Context, id string) (*domain.DeploymentTarget, error) {
|
|
return &domain.DeploymentTarget{
|
|
ID: id,
|
|
Name: "NGINX Proxy",
|
|
Type: "nginx",
|
|
AgentID: "agent-001",
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/t-nginx-01", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetTarget(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetTarget_NotFound(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
GetTargetFn: func(_ context.Context, id string) (*domain.DeploymentTarget, error) {
|
|
return nil, ErrMockNotFound
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/nonexistent", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetTarget(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetTarget_EmptyID(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockTargetService{
|
|
CreateTargetFn: func(_ context.Context, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
target.ID = "t-new"
|
|
target.CreatedAt = now
|
|
target.UpdatedAt = now
|
|
return &target, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "New Target",
|
|
"type": "nginx",
|
|
"agent_id": "agent-001",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("expected status 201, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_MissingName(t *testing.T) {
|
|
body := map[string]interface{}{
|
|
"type": "nginx",
|
|
"agent_id": "agent-001",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_MissingType(t *testing.T) {
|
|
body := map[string]interface{}{
|
|
"name": "New Target",
|
|
"agent_id": "agent-001",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_InvalidJSON(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader([]byte("not json")))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_NameTooLong(t *testing.T) {
|
|
longName := ""
|
|
for i := 0; i < 256; i++ {
|
|
longName += "x"
|
|
}
|
|
body := map[string]interface{}{
|
|
"name": longName,
|
|
"type": "nginx",
|
|
"agent_id": "agent-001",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTarget_MethodNotAllowed(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCreateTarget_MissingAgentID_Returns400 pins the C-002 handler contract:
|
|
// handler MUST reject a create payload that omits agent_id with HTTP 400
|
|
// before the service is invoked. Using a mock that would return 201-worthy
|
|
// success proves the guard fires.
|
|
func TestCreateTarget_MissingAgentID_Returns400(t *testing.T) {
|
|
body := map[string]interface{}{
|
|
"name": "New Target",
|
|
"type": "nginx",
|
|
// agent_id intentionally omitted
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
mock := &MockTargetService{
|
|
CreateTargetFn: func(_ context.Context, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
// Would succeed if handler guard did not fire.
|
|
target.ID = "t-would-be-created"
|
|
return &target, nil
|
|
},
|
|
}
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d — body=%s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestCreateTarget_NonexistentAgent_Returns400 pins the C-002 handler↔service
|
|
// translation: when the service returns service.ErrAgentNotFound, the handler
|
|
// MUST map it to HTTP 400, not the generic 500 used for other service errors.
|
|
func TestCreateTarget_NonexistentAgent_Returns400(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
CreateTargetFn: func(_ context.Context, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
return nil, service.ErrAgentNotFound
|
|
},
|
|
}
|
|
body := map[string]interface{}{
|
|
"name": "New Target",
|
|
"type": "nginx",
|
|
"agent_id": "agent-does-not-exist",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 for nonexistent agent, got %d — body=%s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestUpdateTarget_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockTargetService{
|
|
UpdateTargetFn: func(_ context.Context, id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
|
return &domain.DeploymentTarget{
|
|
ID: id,
|
|
Name: target.Name,
|
|
Type: "nginx",
|
|
AgentID: "agent-001",
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "Updated Target",
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/targets/t-nginx-01", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.UpdateTarget(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteTarget_Success(t *testing.T) {
|
|
var deletedID string
|
|
mock := &MockTargetService{
|
|
DeleteTargetFn: func(_ context.Context, id string) error {
|
|
deletedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/t-nginx-01", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteTarget(w, req)
|
|
|
|
if w.Code != http.StatusNoContent {
|
|
t.Fatalf("expected status 204, got %d", w.Code)
|
|
}
|
|
if deletedID != "t-nginx-01" {
|
|
t.Errorf("expected deleted ID 't-nginx-01', got '%s'", deletedID)
|
|
}
|
|
}
|
|
|
|
func TestDeleteTarget_ServiceError(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
DeleteTargetFn: func(_ context.Context, id string) error {
|
|
return ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/t-nginx-01", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteTarget(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected status 500, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteTarget_EmptyID(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteTarget(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestTestTargetConnection_Success(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
TestConnectionFn: func(_ context.Context, id string) error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets/t-nginx-01/test", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.TestTargetConnection(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["status"] != "success" {
|
|
t.Errorf("expected status 'success', got %v", resp["status"])
|
|
}
|
|
}
|
|
|
|
func TestTestTargetConnection_Failed(t *testing.T) {
|
|
mock := &MockTargetService{
|
|
TestConnectionFn: func(_ context.Context, id string) error {
|
|
return ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewTargetHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets/t-nginx-01/test", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.TestTargetConnection(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["status"] != "failed" {
|
|
t.Errorf("expected status 'failed', got %v", resp["status"])
|
|
}
|
|
}
|
|
|
|
func TestTestTargetConnection_MethodNotAllowed(t *testing.T) {
|
|
handler := NewTargetHandler(&MockTargetService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/t-nginx-01/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.TestTargetConnection(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|