mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 04:14:56 +00:00
Extract wake_heartbeat and stateless from JSON payload into first-class columns on cron_jobs. Adds migration 000033 with backfill from existing payload data. Updates PG + SQLite stores, RPC handlers, and UI i18n.
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package pg
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// TestScheduleKindCorruption_LogicPath verifies that empty schedule_kind
|
|
// (which would happen if the pre-fetch Scan failed and error was ignored)
|
|
// falls through validation with no error — proving why the Scan error
|
|
// MUST be checked (as it now is in the fix).
|
|
//
|
|
// The fix: UpdateJob now returns an error when the schedule-fetch Scan fails,
|
|
// so this corrupted logic path is no longer reachable.
|
|
func TestScheduleKindCorruption_LogicPath(t *testing.T) {
|
|
// When Scan fails, curKind is "" (zero value).
|
|
// With partial patch (no kind specified), newKind inherits "".
|
|
curKind := "" // simulates failed Scan
|
|
patchKind := ""
|
|
|
|
newKind := patchKind
|
|
if newKind == "" {
|
|
newKind = curKind
|
|
}
|
|
|
|
// Verify the corruption: empty kind bypasses all validation cases
|
|
merged := store.CronSchedule{Kind: newKind}
|
|
validationRan := false
|
|
switch merged.Kind {
|
|
case "cron", "every", "at":
|
|
validationRan = true
|
|
}
|
|
|
|
if validationRan {
|
|
t.Error("expected validation to be skipped for empty kind")
|
|
}
|
|
if newKind != "" {
|
|
t.Error("expected empty newKind from failed Scan")
|
|
}
|
|
|
|
// This proves the corruption path exists.
|
|
// The fix (returning error from Scan) prevents reaching this path.
|
|
t.Log("Confirmed: empty schedule_kind bypasses validation — fix prevents this by checking Scan error")
|
|
}
|
|
|
|
// TestPayloadUnmarshal_CorruptedJSON verifies that corrupted JSON in the
|
|
// payload column causes json.Unmarshal to fail, which would wipe existing
|
|
// fields if the error is ignored — proving why the error MUST be checked.
|
|
//
|
|
// The fix: UpdateJob now returns an error when payload Unmarshal fails,
|
|
// so existing fields are never overwritten with zero values.
|
|
func TestPayloadUnmarshal_CorruptedJSON(t *testing.T) {
|
|
corruptedJSON := []byte(`{"kind":"message","message":"daily report",CORRUPT`)
|
|
|
|
var payload store.CronPayload
|
|
err := json.Unmarshal(corruptedJSON, &payload)
|
|
|
|
// Prove that Unmarshal fails
|
|
if err == nil {
|
|
t.Fatal("expected json.Unmarshal to fail on corrupted JSON")
|
|
}
|
|
|
|
// Prove that payload is zero-valued after failed Unmarshal
|
|
if payload.Message != "" {
|
|
t.Error("expected empty Message after failed Unmarshal")
|
|
}
|
|
|
|
// If we ignored this error and applied patch fields, we'd wipe existing data.
|
|
// The fix returns an error instead.
|
|
t.Log("Confirmed: failed Unmarshal leaves zero payload — fix prevents wipe by returning error")
|
|
}
|
|
|
|
// TestPayloadUnmarshal_ValidJSON verifies normal payload round-trip works.
|
|
// Deliver/DeliverChannel/DeliverTo are now dedicated columns on CronJob, not payload fields.
|
|
func TestPayloadUnmarshal_ValidJSON(t *testing.T) {
|
|
original := store.CronPayload{
|
|
Kind: "message",
|
|
Message: "daily report",
|
|
}
|
|
data, _ := json.Marshal(original)
|
|
|
|
var decoded store.CronPayload
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if decoded.Message != original.Message {
|
|
t.Errorf("Message mismatch: got %q, want %q", decoded.Message, original.Message)
|
|
}
|
|
}
|