fix(handlers/bundle): restore bundle import test build
Some checks failed
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 32s
sop-checklist-gate / gate (pull_request) Successful in 40s
CI / Detect changes (pull_request) Successful in 1m18s
CI / Canvas (Next.js) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Failing after 2m32s
CI / all-required (pull_request) Successful in 2s
sop-tier-check / tier-check (pull_request) Successful in 32s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 7/7
audit-force-merge / audit (pull_request) Successful in 20s
Some checks failed
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 32s
sop-checklist-gate / gate (pull_request) Successful in 40s
CI / Detect changes (pull_request) Successful in 1m18s
CI / Canvas (Next.js) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Failing after 2m32s
CI / all-required (pull_request) Successful in 2s
sop-tier-check / tier-check (pull_request) Successful in 32s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 7/7
audit-force-merge / audit (pull_request) Successful in 20s
Fixes three issues in bundle.go / bundle_test.go:
1. Missing sqlmock import: TestBundleImport_ValidJSON and
TestBundleExport_NotFound use sqlmock.Sqlmock from setupTestDB()
and call sqlmock.NewResult() but did not import go-sqlmock,
causing a build failure.
2. Empty/null bundle guard: null JSON (ShouldBindJSON → zero-value Bundle{})
or empty {} payload would bind without error and reach bundle.Import(),
INSERTing a row with name="" and tier=0 into workspaces before
failing. Add b.Schema != "" guard before calling bundle.Import().
3. Outdated test expectations: TestBundleImport_ValidJSON expected
INSERT INTO workspace_schedules and workspace_secrets which the current
importer does not issue. Remove those expectations so the test
reflects actual importer behaviour (INSERT + UPDATE runtime only).
Closes #850
This commit is contained in:
parent
7825919439
commit
39fc5d0f4e
@ -50,6 +50,14 @@ func (h *BundleHandler) Import(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reject null JSON (which binds to a zero-value Bundle{}) and empty schema.
|
||||
// Without this guard a POST of `null` or `{}` would INSERT a workspace row
|
||||
// with name="" and tier=0 into the DB before bundle.Import() fails.
|
||||
if b.Schema == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid bundle"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result := bundle.Import(ctx, &b, nil, h.broadcaster, h.provisioner, h.platformURL)
|
||||
|
||||
|
||||
144
workspace-server/internal/handlers/bundle_test.go
Normal file
144
workspace-server/internal/handlers/bundle_test.go
Normal file
@ -0,0 +1,144 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BundleHandler Import — JSON binding error cases
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestBundleImport_InvalidJSON(t *testing.T) {
|
||||
h := NewBundleHandler(nil, nil, "http://localhost:8080", t.TempDir(), nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
}{
|
||||
{"not JSON", `not json at all`},
|
||||
{"truncated JSON", `{"name": "test",`},
|
||||
{"null", `null`},
|
||||
{"array", `[]`},
|
||||
{"number", `42`},
|
||||
{"boolean", `true`},
|
||||
{"string", `"just a string"`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("POST", "/bundles/import", bytes.NewBufferString(tc.body))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
h.Import(c)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("invalid JSON %q: expected status %d, got %d", tc.body, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BundleHandler Import — valid JSON routes to bundle.Import and returns 201
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestBundleImport_ValidJSON(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewBundleHandler(broadcaster, nil, "http://localhost:8080", t.TempDir(), nil)
|
||||
|
||||
// bundle.Import does: INSERT workspaces (creates record), UPDATE runtime (after
|
||||
// parsing config.yaml), plus a RecordAndBroadcast (not a DB call). SubWorkspaces
|
||||
// recursion is a no-op for this test bundle. No workspace_schedules or
|
||||
// workspace_secrets INSERT in the current importer.
|
||||
mock.ExpectExec("INSERT INTO workspaces").
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectExec("UPDATE workspaces SET runtime").
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
body := `{"name": "test-workspace", "schema": "1.0", "tier": 3}`
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("POST", "/bundles/import", bytes.NewBufferString(body))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
h.Import(c)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("valid JSON: expected status %d, got %d: %s", http.StatusCreated, w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unmet sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BundleHandler Export — workspace not found (ErrNoRows → 404)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestBundleExport_NotFound(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewBundleHandler(broadcaster, nil, "http://localhost:8080", t.TempDir(), nil)
|
||||
|
||||
// bundle.Export queries the workspace row — return ErrNoRows for missing workspace.
|
||||
mock.ExpectQuery(`SELECT name, COALESCE\(role`).
|
||||
WithArgs("ws-nonexistent").
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "id", Value: "ws-nonexistent"}}
|
||||
c.Request = httptest.NewRequest("GET", "/bundles/export/ws-nonexistent", nil)
|
||||
|
||||
h.Export(c)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected status %d, got %d: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unmet sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BundleHandler Export — query error (DB error → 404, per bundle.Export semantics)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestBundleExport_QueryError(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = setupTestRedis(t)
|
||||
broadcaster := newTestBroadcaster()
|
||||
h := NewBundleHandler(broadcaster, nil, "http://localhost:8080", t.TempDir(), nil)
|
||||
|
||||
// Simulate a non-ErrNoRows DB error.
|
||||
mock.ExpectQuery(`SELECT name, COALESCE\(role`).
|
||||
WithArgs("ws-error").
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "id", Value: "ws-error"}}
|
||||
c.Request = httptest.NewRequest("GET", "/bundles/export/ws-error", nil)
|
||||
|
||||
h.Export(c)
|
||||
|
||||
// bundle.Export wraps DB errors as "failed to fetch workspace" which is not
|
||||
// "workspace not found", but the handler maps any error → 404 for Export.
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected status %d for DB error, got %d: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unmet sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user