test(registry): regression for boot Register non-200 log (#2615) #2630
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user