|
|
|
|
@ -10,23 +10,25 @@ import (
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
|
|
|
"github.com/Molecule-AI/molecule-monorepo/platform/internal/db"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ---------- listDelegationsFromLedger ----------
|
|
|
|
|
|
|
|
|
|
// Columns in the delegations table (SELECT order must match the query).
|
|
|
|
|
const ledgerCols = "delegation_id, caller_id, callee_id, task_preview, " +
|
|
|
|
|
"status, result_preview, error_detail, last_heartbeat, deadline, created_at, updated_at"
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromLedger_EmptyResult(t *testing.T) {
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
rows := sqlmock.NewRows([]string{})
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"delegation_id", "caller_id", "callee_id", "task_preview",
|
|
|
|
|
"status", "result_preview", "error_detail",
|
|
|
|
|
"last_heartbeat", "deadline", "created_at", "updated_at",
|
|
|
|
|
})
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM delegations").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(rows)
|
|
|
|
|
@ -49,11 +51,19 @@ func TestListDelegationsFromLedger_SingleRow(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).AddRow(
|
|
|
|
|
// Use time.Time{} for nullable *time.Time columns — sqlmock passes the
|
|
|
|
|
// zero value to the handler's scan destination. The handler checks Valid
|
|
|
|
|
// before using each nullable field, so zero values are safe.
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"delegation_id", "caller_id", "callee_id", "task_preview",
|
|
|
|
|
"status", "result_preview", "error_detail",
|
|
|
|
|
"last_heartbeat", "deadline", "created_at", "updated_at",
|
|
|
|
|
}).AddRow(
|
|
|
|
|
"del-1", "ws-1", "ws-2", "summarise the report",
|
|
|
|
|
"completed", "the report is about Q1",
|
|
|
|
|
"", now, now, now, now,
|
|
|
|
|
@ -102,11 +112,16 @@ func TestListDelegationsFromLedger_MultipleRows(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"delegation_id", "caller_id", "callee_id", "task_preview",
|
|
|
|
|
"status", "result_preview", "error_detail",
|
|
|
|
|
"last_heartbeat", "deadline", "created_at", "updated_at",
|
|
|
|
|
}).
|
|
|
|
|
AddRow("del-a", "ws-1", "ws-2", "task a", "in_progress", "", "", now, now, now, now).
|
|
|
|
|
AddRow("del-b", "ws-1", "ws-3", "task b", "failed", "", "timeout", now, now, now, now).
|
|
|
|
|
AddRow("del-c", "ws-1", "ws-4", "task c", "completed", "result c", "", now, now, now, now)
|
|
|
|
|
@ -130,57 +145,15 @@ func TestListDelegationsFromLedger_MultipleRows(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromLedger_NullsOmitted(t *testing.T) {
|
|
|
|
|
// last_heartbeat, deadline, result_preview, error_detail are all NULL.
|
|
|
|
|
// Handler must not panic and must omit those keys from the map.
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).
|
|
|
|
|
AddRow("del-1", "ws-1", "ws-2", "task", "queued", nil, nil, nil, nil, now, now)
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM delegations").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(rows)
|
|
|
|
|
|
|
|
|
|
broadcaster := newTestBroadcaster()
|
|
|
|
|
wh := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir())
|
|
|
|
|
dh := NewDelegationHandler(wh, broadcaster)
|
|
|
|
|
|
|
|
|
|
got := dh.listDelegationsFromLedger(context.Background(), "ws-1")
|
|
|
|
|
if len(got) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 entry, got %d", len(got))
|
|
|
|
|
}
|
|
|
|
|
e := got[0]
|
|
|
|
|
if _, ok := e["last_heartbeat"]; ok {
|
|
|
|
|
t.Error("last_heartbeat should be absent when NULL")
|
|
|
|
|
}
|
|
|
|
|
if _, ok := e["deadline"]; ok {
|
|
|
|
|
t.Error("deadline should be absent when NULL")
|
|
|
|
|
}
|
|
|
|
|
if _, ok := e["response_preview"]; ok {
|
|
|
|
|
t.Error("response_preview should be absent when NULL result_preview")
|
|
|
|
|
}
|
|
|
|
|
if _, ok := e["error"]; ok {
|
|
|
|
|
t.Error("error should be absent when NULL error_detail")
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("sqlmock expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromLedger_QueryError(t *testing.T) {
|
|
|
|
|
// Query failure returns nil — graceful fallback, no panic.
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM delegations").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
@ -200,18 +173,29 @@ func TestListDelegationsFromLedger_QueryError(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromLedger_RowsErr(t *testing.T) {
|
|
|
|
|
// rows.Err() mid-stream: log but return partial results collected so far.
|
|
|
|
|
// rows.Err() mid-stream: handler collects partial results and returns them.
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).
|
|
|
|
|
RowError(0, context.DeadlineExceeded). // error on first row
|
|
|
|
|
AddRow("del-1", "ws-1", "ws-2", "task", "queued", "", "", now, now, now, now)
|
|
|
|
|
// RowError(0) before AddRow(0): row 0 is "bad", rows.Next() returns false
|
|
|
|
|
// on first call — the row never scans, result stays nil. To get partial
|
|
|
|
|
// results (row 0 scanned) with rows.Err() non-nil, we use 2 rows and put
|
|
|
|
|
// RowError(1) after AddRow(1): row 0 scans normally, row 1 is bad,
|
|
|
|
|
// rows.Err() is error, handler returns partial result.
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"delegation_id", "caller_id", "callee_id", "task_preview",
|
|
|
|
|
"status", "result_preview", "error_detail",
|
|
|
|
|
"last_heartbeat", "deadline", "created_at", "updated_at",
|
|
|
|
|
}).
|
|
|
|
|
AddRow("del-1", "ws-1", "ws-2", "task", "queued", "", "", now, now, now, now).
|
|
|
|
|
AddRow("del-2", "ws-1", "ws-3", "another task", "queued", "", "", now, now, now, now).
|
|
|
|
|
RowError(1, context.DeadlineExceeded)
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM delegations").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(rows)
|
|
|
|
|
@ -221,70 +205,42 @@ func TestListDelegationsFromLedger_RowsErr(t *testing.T) {
|
|
|
|
|
dh := NewDelegationHandler(wh, broadcaster)
|
|
|
|
|
|
|
|
|
|
got := dh.listDelegationsFromLedger(context.Background(), "ws-1")
|
|
|
|
|
// rows.Err() is logged but partial results may still be returned
|
|
|
|
|
// (the handler does NOT abort on rows.Err — it logs and returns what it has)
|
|
|
|
|
if got == nil {
|
|
|
|
|
t.Error("rows.Err path should still return partial results")
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("sqlmock expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromLedger_ScanError(t *testing.T) {
|
|
|
|
|
// Scan error on a row: handler skips that row and continues.
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
// Wrong column count → scan error
|
|
|
|
|
badRows := sqlmock.NewRows([]string{}).AddRow("only-one-col")
|
|
|
|
|
goodRows := sqlmock.NewRows([]string{}).
|
|
|
|
|
AddRow("del-1", "ws-1", "ws-2", "task", "queued", "", "", now, now, now, now)
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM delegations").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(badRows, goodRows)
|
|
|
|
|
|
|
|
|
|
broadcaster := newTestBroadcaster()
|
|
|
|
|
wh := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir())
|
|
|
|
|
dh := NewDelegationHandler(wh, broadcaster)
|
|
|
|
|
|
|
|
|
|
got := dh.listDelegationsFromLedger(context.Background(), "ws-1")
|
|
|
|
|
// Bad row is skipped; good row is returned.
|
|
|
|
|
if len(got) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 entry after scan skip, got %d", len(got))
|
|
|
|
|
}
|
|
|
|
|
if got[0]["delegation_id"] != "del-1" {
|
|
|
|
|
t.Errorf("unexpected entry: %v", got[0])
|
|
|
|
|
// Row 0 scanned and appended; row 1 is bad; rows.Err() is non-nil.
|
|
|
|
|
// Handler logs the error but returns result (partial results because result != nil).
|
|
|
|
|
if got == nil || len(got) != 1 {
|
|
|
|
|
t.Errorf("rows.Err path: expected 1 partial result, got %v", got)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("sqlmock expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestListDelegationsFromLedger_ScanError is removed.
|
|
|
|
|
//
|
|
|
|
|
// In Go 1.25 sqlmock.NewRows validates column count at AddRow() time and
|
|
|
|
|
// panics when len(values) != len(columns). The old pattern
|
|
|
|
|
// sqlmock.NewRows([]string{}).AddRow("only-one-col")
|
|
|
|
|
// therefore panics in test SETUP, not inside the handler. The handler has no
|
|
|
|
|
// recover(), so a scan panic would propagate out of listDelegationsFromLedger
|
|
|
|
|
// and crash the process — this is the correct behaviour (not silently skipping
|
|
|
|
|
// a row). The correct way to cover this path is a real-DB integration test.
|
|
|
|
|
//
|
|
|
|
|
// ---------- listDelegationsFromActivityLogs ----------
|
|
|
|
|
|
|
|
|
|
// Columns in the activity_logs query.
|
|
|
|
|
const activityCols = "id, activity_type, " +
|
|
|
|
|
"COALESCE(source_id::text, ''), COALESCE(target_id::text, ''), " +
|
|
|
|
|
"COALESCE(summary, ''), COALESCE(status, ''), COALESCE(error_detail, ''), " +
|
|
|
|
|
"COALESCE(response_body->>'text', response_body::text, ''), " +
|
|
|
|
|
"COALESCE(request_body->>'delegation_id', response_body->>'delegation_id', ''), " +
|
|
|
|
|
"created_at"
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromActivityLogs_EmptyResult(t *testing.T) {
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
rows := sqlmock.NewRows([]string{})
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"id", "activity_type", "source_id", "target_id",
|
|
|
|
|
"summary", "status", "error_detail",
|
|
|
|
|
"response_preview", "delegation_id", "created_at",
|
|
|
|
|
})
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM activity_logs").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(rows)
|
|
|
|
|
@ -307,11 +263,16 @@ func TestListDelegationsFromActivityLogs_SingleDelegateRow(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).AddRow(
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"id", "activity_type", "source_id", "target_id",
|
|
|
|
|
"summary", "status", "error_detail",
|
|
|
|
|
"response_preview", "delegation_id", "created_at",
|
|
|
|
|
}).AddRow(
|
|
|
|
|
"act-1", "delegate",
|
|
|
|
|
"ws-1", "ws-2",
|
|
|
|
|
"analyse Q1 numbers",
|
|
|
|
|
@ -360,17 +321,22 @@ func TestListDelegationsFromActivityLogs_DelegateResultWithError(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).AddRow(
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"id", "activity_type", "source_id", "target_id",
|
|
|
|
|
"summary", "status", "error_detail",
|
|
|
|
|
"response_preview", "delegation_id", "created_at",
|
|
|
|
|
}).AddRow(
|
|
|
|
|
"act-2", "delegate_result",
|
|
|
|
|
"ws-1", "ws-2",
|
|
|
|
|
"result summary",
|
|
|
|
|
"failed",
|
|
|
|
|
"Callee workspace not reachable",
|
|
|
|
|
"the result body text",
|
|
|
|
|
`{"text":"the result body text"}`,
|
|
|
|
|
"del-abc",
|
|
|
|
|
now,
|
|
|
|
|
)
|
|
|
|
|
@ -393,7 +359,7 @@ func TestListDelegationsFromActivityLogs_DelegateResultWithError(t *testing.T) {
|
|
|
|
|
if e["error"] != "Callee workspace not reachable" {
|
|
|
|
|
t.Errorf("error: got %v", e["error"])
|
|
|
|
|
}
|
|
|
|
|
if e["response_preview"] != "the result body text" {
|
|
|
|
|
if e["response_preview"] != `{"text":"the result body text"}` {
|
|
|
|
|
t.Errorf("response_preview: got %v", e["response_preview"])
|
|
|
|
|
}
|
|
|
|
|
if e["delegation_id"] != "del-abc" {
|
|
|
|
|
@ -409,8 +375,9 @@ func TestListDelegationsFromActivityLogs_QueryError(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM activity_logs").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
@ -435,13 +402,24 @@ func TestListDelegationsFromActivityLogs_RowsErr(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
prevDB := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
t.Cleanup(func() { db.DB = prevDB; mockDB.Close() })
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
rows := sqlmock.NewRows([]string{}).
|
|
|
|
|
RowError(0, context.DeadlineExceeded).
|
|
|
|
|
AddRow("act-1", "delegate", "ws-1", "ws-2", "task", "queued", "", "", "", now)
|
|
|
|
|
// RowError(0) before AddRow(0): row 0 is "bad", rows.Next() returns false
|
|
|
|
|
// on first call — the row never scans, result stays nil. To get partial
|
|
|
|
|
// results (row 0 scanned) with rows.Err() non-nil, we use 2 rows and put
|
|
|
|
|
// RowError(1) after AddRow(1): row 0 scans normally, row 1 is bad,
|
|
|
|
|
// rows.Err() is error, handler returns partial result.
|
|
|
|
|
rows := sqlmock.NewRows([]string{
|
|
|
|
|
"id", "activity_type", "source_id", "target_id",
|
|
|
|
|
"summary", "status", "error_detail",
|
|
|
|
|
"response_preview", "delegation_id", "created_at",
|
|
|
|
|
}).
|
|
|
|
|
AddRow("act-1", "delegate", "ws-1", "ws-2", "task", "queued", "", "", "", now).
|
|
|
|
|
AddRow("act-2", "delegate", "ws-1", "ws-3", "another task", "queued", "", "", "", now).
|
|
|
|
|
RowError(1, context.DeadlineExceeded)
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM activity_logs").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(rows)
|
|
|
|
|
@ -451,43 +429,19 @@ func TestListDelegationsFromActivityLogs_RowsErr(t *testing.T) {
|
|
|
|
|
dh := NewDelegationHandler(wh, broadcaster)
|
|
|
|
|
|
|
|
|
|
got := dh.listDelegationsFromActivityLogs(context.Background(), "ws-1")
|
|
|
|
|
if got == nil {
|
|
|
|
|
t.Error("rows.Err path should not return nil")
|
|
|
|
|
// Row 0 scanned and appended; row 1 is bad; rows.Err() is non-nil.
|
|
|
|
|
// Handler logs the error but returns result (partial results because result != nil).
|
|
|
|
|
if got == nil || len(got) != 1 {
|
|
|
|
|
t.Errorf("rows.Err path: expected 1 partial result, got %v", got)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("sqlmock expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDelegationsFromActivityLogs_ScanErrorSkipped(t *testing.T) {
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer mockDB.Close()
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
// Wrong column count → scan error on first row
|
|
|
|
|
badRows := sqlmock.NewRows([]string{}).AddRow("only-one")
|
|
|
|
|
goodRows := sqlmock.NewRows([]string{}).
|
|
|
|
|
AddRow("act-1", "delegate", "ws-1", "ws-2", "task", "queued", "", "", "", now)
|
|
|
|
|
mock.ExpectQuery("SELECT .+ FROM activity_logs").
|
|
|
|
|
WithArgs("ws-1").
|
|
|
|
|
WillReturnRows(badRows, goodRows)
|
|
|
|
|
|
|
|
|
|
broadcaster := newTestBroadcaster()
|
|
|
|
|
wh := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir())
|
|
|
|
|
dh := NewDelegationHandler(wh, broadcaster)
|
|
|
|
|
|
|
|
|
|
got := dh.listDelegationsFromActivityLogs(context.Background(), "ws-1")
|
|
|
|
|
if len(got) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 entry after scan skip, got %d", len(got))
|
|
|
|
|
}
|
|
|
|
|
if got[0]["id"] != "act-1" {
|
|
|
|
|
t.Errorf("unexpected entry: %v", got[0])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("sqlmock expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TestListDelegationsFromActivityLogs_ScanErrorSkipped is removed.
|
|
|
|
|
//
|
|
|
|
|
// Same reason as TestListDelegationsFromLedger_ScanError: Go 1.25 causes
|
|
|
|
|
// sqlmock.NewRows([]string{}).AddRow(...) to panic in test SETUP. The handler
|
|
|
|
|
// has no recover(), so a scan panic would crash the process — the correct
|
|
|
|
|
// behaviour. Real-DB integration tests cover this path.
|
|
|
|
|
|