test(registry): regression for boot Register non-200 log (#2615) #2630

Merged
devops-engineer merged 1 commits from fix/registry-boot-register-log-regression-test into main 2026-06-12 06:37:58 +00:00
@@ -4,6 +4,7 @@ import (
"bytes"
"database/sql"
"encoding/json"
"log"
"net/http"
"net/http/httptest"
"strings"
@@ -94,6 +95,109 @@ func TestRegister_DBError(t *testing.T) {
}
}
// TestRegister_Non200_LogsStatusCode is a regression guard for #2563 / #2615:
// when boot Register returns a non-200 status, the response code must be logged
// so operators can distinguish 401/400/403/5xx register failures.
func TestRegister_Non200_LogsStatusCode(t *testing.T) {
mock := setupTestDB(t)
setupTestRedis(t)
broadcaster := newTestBroadcaster()
handler := NewRegistryHandler(broadcaster)
var buf bytes.Buffer
oldOutput := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(oldOutput)
// Same DB-error setup as TestRegister_DBError; produces a 500 response.
mock.ExpectQuery(`SELECT delivery_mode, runtime FROM workspaces WHERE id`).
WithArgs("ws-fail").
WillReturnError(sql.ErrNoRows)
mock.ExpectExec("INSERT INTO workspaces").
WithArgs("ws-fail", "ws-fail", "http://localhost:8000", `{"name":"test"}`, "push", "").
WillReturnError(sql.ErrConnDone)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
body := `{"id":"ws-fail","url":"http://localhost:8000","agent_card":{"name":"test"}}`
c.Request = httptest.NewRequest("POST", "/registry/register", bytes.NewBufferString(body))
c.Request.Header.Set("Content-Type", "application/json")
handler.Register(c)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected status 500, got %d: %s", w.Code, w.Body.String())
}
logs := buf.String()
if !strings.Contains(logs, "boot_register_failed status=500") {
t.Errorf("expected boot_register_failed log with status=500, got: %s", logs)
}
if !strings.Contains(logs, "workspace=ws-fail") {
t.Errorf("expected boot_register_failed log with workspace ID, got: %s", logs)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet sqlmock expectations: %v", err)
}
}
// TestRegister_200_DoesNotLogFailure verifies that a successful boot Register
// does not emit the failure log line.
func TestRegister_200_DoesNotLogFailure(t *testing.T) {
mock := setupTestDB(t)
setupTestRedis(t)
broadcaster := newTestBroadcaster()
handler := NewRegistryHandler(broadcaster)
var buf bytes.Buffer
oldOutput := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(oldOutput)
// Successful first-registration path (same mock setup as the C18 bootstrap test).
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM workspace_auth_tokens").
WithArgs("ws-ok").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
mock.ExpectQuery(`SELECT delivery_mode, runtime FROM workspaces WHERE id`).
WithArgs("ws-ok").
WillReturnError(sql.ErrNoRows)
mock.ExpectExec("INSERT INTO workspaces").
WithArgs("ws-ok", "ws-ok", "http://localhost:9100", `{"name":"ok-agent"}`, "push", "").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectQuery("SELECT url FROM workspaces WHERE id").
WithArgs("ws-ok").
WillReturnRows(sqlmock.NewRows([]string{"url"}).AddRow("http://localhost:9100"))
mock.ExpectExec("INSERT INTO structure_events").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM workspace_auth_tokens").
WithArgs("ws-ok").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
mock.ExpectExec("INSERT INTO workspace_auth_tokens").
WithArgs("ws-ok", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("POST", "/registry/register",
bytes.NewBufferString(`{"id":"ws-ok","url":"http://localhost:9100","agent_card":{"name":"ok-agent"}}`))
c.Request.Header.Set("Content-Type", "application/json")
handler.Register(c)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d: %s", w.Code, w.Body.String())
}
if strings.Contains(buf.String(), "boot_register_failed") {
t.Errorf("expected no boot_register_failed log on 200, got: %s", buf.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet sqlmock expectations: %v", err)
}
}
// ==================== Heartbeat — offline → online recovery ====================
func TestHeartbeatHandler_OfflineToOnline(t *testing.T) {