- Add parser_map for market data refresh configuration propagation (cli/worker/api) - Implement refresh status model with SQLite persistence (status_store.go) - Add scheduler refresh status headless output to CLI operator - Extend backfill scheduler with status tracking (start/complete/fail) - Add socket events for scheduler refresh status (start/complete/fail) - Update proto definitions for refresh status - Add generated PB files for client
270 lines
6.9 KiB
Go
270 lines
6.9 KiB
Go
package jobs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRunnerDispatchesRegisteredHandler(t *testing.T) {
|
|
runner := NewRunner()
|
|
called := false
|
|
var receivedPayload json.RawMessage
|
|
|
|
testKind := Kind("test_job")
|
|
runner.Register(testKind, func(ctx context.Context, payload json.RawMessage) error {
|
|
called = true
|
|
receivedPayload = payload
|
|
return nil
|
|
})
|
|
|
|
job := Job{
|
|
ID: "job-1",
|
|
Kind: testKind,
|
|
Payload: json.RawMessage(`{"key":"value"}`),
|
|
}
|
|
|
|
err := runner.Execute(context.Background(), job)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if !called {
|
|
t.Error("expected handler to be called, but it was not")
|
|
}
|
|
|
|
if string(receivedPayload) != `{"key":"value"}` {
|
|
t.Errorf("expected payload %q, got %q", `{"key":"value"}`, string(receivedPayload))
|
|
}
|
|
}
|
|
|
|
func TestRunnerRejectsUnknownKind(t *testing.T) {
|
|
runner := NewRunner()
|
|
job := Job{
|
|
ID: "job-2",
|
|
Kind: Kind("non_existent"),
|
|
}
|
|
|
|
err := runner.Execute(context.Background(), job)
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown job kind, got nil")
|
|
}
|
|
}
|
|
|
|
func TestRunnerRespectsCanceledContext(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("cancellation_test")
|
|
|
|
runner.Register(testKind, func(ctx context.Context, payload json.RawMessage) error {
|
|
return nil
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // Cancel context immediately
|
|
|
|
job := Job{
|
|
ID: "job-3",
|
|
Kind: testKind,
|
|
}
|
|
|
|
err := runner.Execute(ctx, job)
|
|
if err == nil {
|
|
t.Fatal("expected error due to canceled context, got nil")
|
|
}
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Errorf("expected error %v, got %v", context.Canceled, err)
|
|
}
|
|
}
|
|
|
|
func TestRunnerHandlesPanic(t *testing.T) {
|
|
runner := NewRunner()
|
|
panicKind := Kind("panic_job")
|
|
|
|
runner.Register(panicKind, func(ctx context.Context, payload json.RawMessage) error {
|
|
panic("something went critically wrong")
|
|
})
|
|
|
|
job := Job{
|
|
ID: "job-4",
|
|
Kind: panicKind,
|
|
}
|
|
|
|
err := runner.Execute(context.Background(), job)
|
|
if err == nil {
|
|
t.Fatal("expected error from panic recovery, got nil")
|
|
}
|
|
if err.Error() != "job panicked: something went critically wrong" {
|
|
t.Errorf("unexpected error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRegisterBuiltins(t *testing.T) {
|
|
runner := NewRunner()
|
|
RegisterBuiltins(runner)
|
|
|
|
expectedCount := 3
|
|
if runner.Len() != expectedCount {
|
|
t.Errorf("expected %d registered built-in handlers, got %d", expectedCount, runner.Len())
|
|
}
|
|
}
|
|
|
|
// --- ExecuteWithResult tests ---
|
|
|
|
func TestRunnerExecuteWithResultSuccess(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("ewr_success")
|
|
|
|
var payload json.RawMessage
|
|
runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
|
|
payload = p
|
|
return nil
|
|
})
|
|
runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
|
|
return Result{Bars: 42, Instruments: 3}, nil
|
|
})
|
|
|
|
job := Job{
|
|
ID: "ewr-1",
|
|
Kind: testKind,
|
|
Payload: json.RawMessage(`{"key":"value"}`),
|
|
}
|
|
|
|
res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
|
|
if err != nil {
|
|
t.Fatalf("ExecuteWithResult: unexpected error: %v", err)
|
|
}
|
|
if !hasResult {
|
|
t.Fatal("ExecuteWithResult: expected hasResult=true, got false")
|
|
}
|
|
if res.Bars != 42 || res.Instruments != 3 {
|
|
t.Errorf("ExecuteWithResult: expected {Bars:42,Instruments:3}, got %+v", res)
|
|
}
|
|
// Verify the handler was called with the correct payload.
|
|
_ = payload // payload is captured by the Register handler; assertion is above.
|
|
}
|
|
|
|
func TestRunnerExecuteWithResultMissingHandler(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("ewr_missing")
|
|
|
|
// Only register Handler, not WithResultHandler
|
|
runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
|
|
return nil
|
|
})
|
|
|
|
job := Job{
|
|
ID: "ewr-2",
|
|
Kind: testKind,
|
|
}
|
|
|
|
res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
|
|
if err != nil {
|
|
t.Fatalf("ExecuteWithResult: expected no error for missing handler, got: %v", err)
|
|
}
|
|
if hasResult {
|
|
t.Fatal("ExecuteWithResult: expected hasResult=false for missing handler, got true")
|
|
}
|
|
if res.Bars != 0 {
|
|
t.Errorf("ExecuteWithResult: expected zero result for missing handler, got %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestRunnerExecuteWithResultHandlerError(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("ewr_error")
|
|
wantErr := fmt.Errorf("import failed: connection reset")
|
|
|
|
runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
|
|
return nil
|
|
})
|
|
runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
|
|
return Result{}, wantErr
|
|
})
|
|
|
|
job := Job{
|
|
ID: "ewr-3",
|
|
Kind: testKind,
|
|
}
|
|
|
|
res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
|
|
if err == nil {
|
|
t.Fatal("ExecuteWithResult: expected error from handler, got nil")
|
|
}
|
|
if err != wantErr {
|
|
t.Errorf("ExecuteWithResult: expected error %v, got %v", wantErr, err)
|
|
}
|
|
if hasResult {
|
|
t.Fatal("ExecuteWithResult: expected hasResult=false on error, got true")
|
|
}
|
|
if res.Bars != 0 {
|
|
t.Errorf("ExecuteWithResult: expected zero result on error, got %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestRunnerExecuteWithResultCanceledContext(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("ewr_cancel")
|
|
|
|
runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
|
|
return nil
|
|
})
|
|
runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
|
|
return Result{Bars: 10}, nil
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // Cancel immediately
|
|
|
|
job := Job{
|
|
ID: "ewr-4",
|
|
Kind: testKind,
|
|
}
|
|
|
|
res, hasResult, err := runner.ExecuteWithResult(ctx, job)
|
|
if err == nil {
|
|
t.Fatal("ExecuteWithResult: expected context.Canceled error, got nil")
|
|
}
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Errorf("ExecuteWithResult: expected context.Canceled, got %v", err)
|
|
}
|
|
if hasResult {
|
|
t.Fatal("ExecuteWithResult: expected hasResult=false on cancel, got true")
|
|
}
|
|
if res.Bars != 0 {
|
|
t.Errorf("ExecuteWithResult: expected zero result on cancel, got %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestRunnerExecuteWithResultPanic(t *testing.T) {
|
|
runner := NewRunner()
|
|
testKind := Kind("ewr_panic")
|
|
|
|
runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
|
|
return nil
|
|
})
|
|
runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
|
|
panic("boom in handler")
|
|
})
|
|
|
|
job := Job{
|
|
ID: "ewr-5",
|
|
Kind: testKind,
|
|
}
|
|
|
|
res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
|
|
if err == nil {
|
|
t.Fatal("ExecuteWithResult: expected panic error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "panicked") {
|
|
t.Errorf("ExecuteWithResult: expected 'panicked' in error message, got: %v", err)
|
|
}
|
|
if hasResult {
|
|
t.Fatal("ExecuteWithResult: expected hasResult=false on panic, got true")
|
|
}
|
|
if res.Bars != 0 {
|
|
t.Errorf("ExecuteWithResult: expected zero result on panic, got %+v", res)
|
|
}
|
|
}
|