170 lines
4.6 KiB
Go
170 lines
4.6 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.toki-labs.com/toki/alt/packages/domain/market"
|
|
)
|
|
|
|
func testCapability() market.ProviderCapability {
|
|
return market.ProviderCapability{
|
|
Provider: market.ProviderKIS,
|
|
Rules: []market.ProviderCapabilityRule{
|
|
{
|
|
Market: market.MarketKR,
|
|
Venue: market.VenueKRX,
|
|
AssetTypes: map[market.AssetType]bool{
|
|
market.AssetTypeEquity: true,
|
|
},
|
|
Timeframes: map[market.Timeframe]bool{
|
|
market.TimeframeDaily: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestValidateConfigReportsNextWindowAndRejectedCombination(t *testing.T) {
|
|
cfg, err := LoadConfig(strings.NewReader(`
|
|
schedules:
|
|
- name: kr-core-daily
|
|
provider: kis
|
|
selector:
|
|
kind: watchlist
|
|
market: KR
|
|
venue: KRX
|
|
name: kr-core
|
|
symbols: ["005930", "000660"]
|
|
timeframe: daily
|
|
cadence: daily
|
|
timezone: Asia/Seoul
|
|
backfill_window: 3d
|
|
parallelism_limit: 2
|
|
- name: kr-core-monthly
|
|
provider: kis
|
|
selector:
|
|
kind: watchlist
|
|
market: KR
|
|
venue: KRX
|
|
name: kr-core
|
|
symbols: ["005930"]
|
|
timeframe: monthly
|
|
cadence: daily
|
|
timezone: Asia/Seoul
|
|
backfill_window: 3d
|
|
parallelism_limit: 1
|
|
`))
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig: %v", err)
|
|
}
|
|
now := time.Date(2026, 6, 21, 10, 30, 0, 0, time.UTC)
|
|
result := ValidateConfig(cfg, ValidationOptions{
|
|
Now: now,
|
|
Capabilities: CapabilityMap(testCapability()),
|
|
})
|
|
|
|
if len(result.Items) != 2 {
|
|
t.Fatalf("items = %d, want 2", len(result.Items))
|
|
}
|
|
valid := result.Items[0]
|
|
if valid.Status != "valid" {
|
|
t.Fatalf("first status = %q, want valid: %+v", valid.Status, valid)
|
|
}
|
|
if valid.NextRun != "2026-06-22T00:00:00+09:00" {
|
|
t.Errorf("next run = %q, want 2026-06-22T00:00:00+09:00", valid.NextRun)
|
|
}
|
|
if valid.Cadence != "24h0m0s" || valid.BackfillWindow != "72h0m0s" {
|
|
t.Errorf("durations = %s/%s, want 24h0m0s/72h0m0s", valid.Cadence, valid.BackfillWindow)
|
|
}
|
|
rejected := result.Items[1]
|
|
if rejected.Status != "rejected" {
|
|
t.Fatalf("second status = %q, want rejected", rejected.Status)
|
|
}
|
|
if !strings.Contains(rejected.Reason, "unsupported timeframe") {
|
|
t.Errorf("rejection reason %q missing unsupported timeframe", rejected.Reason)
|
|
}
|
|
}
|
|
|
|
func TestWriteTextAndJSONL(t *testing.T) {
|
|
validation := Validation{Items: []ValidationItem{
|
|
{
|
|
Schedule: "kr-core-daily",
|
|
Status: "valid",
|
|
Provider: "kis",
|
|
Selector: "kr-core",
|
|
Timeframe: "daily",
|
|
Cadence: "24h0m0s",
|
|
Timezone: "Asia/Seoul",
|
|
BackfillWindow: "72h0m0s",
|
|
ParallelismLimit: 2,
|
|
NextRun: "2026-06-22T00:00:00+09:00",
|
|
},
|
|
{Schedule: "bad", Status: "rejected", Reason: "unsupported provider/timeframe combination"},
|
|
}}
|
|
|
|
var text bytes.Buffer
|
|
if err := WriteText(&text, validation); err != nil {
|
|
t.Fatalf("WriteText: %v", err)
|
|
}
|
|
for _, want := range []string{
|
|
"schedule=kr-core-daily status=valid",
|
|
"next_run=2026-06-22T00:00:00+09:00",
|
|
"schedule=bad status=rejected",
|
|
"unsupported provider/timeframe combination",
|
|
} {
|
|
if !strings.Contains(text.String(), want) {
|
|
t.Errorf("text output %q missing %q", text.String(), want)
|
|
}
|
|
}
|
|
|
|
var jsonl bytes.Buffer
|
|
if err := WriteJSONL(&jsonl, validation); err != nil {
|
|
t.Fatalf("WriteJSONL: %v", err)
|
|
}
|
|
if !strings.Contains(jsonl.String(), `"schedule":"kr-core-daily"`) {
|
|
t.Errorf("jsonl output %q missing valid schedule", jsonl.String())
|
|
}
|
|
if !strings.Contains(jsonl.String(), `"status":"rejected"`) {
|
|
t.Errorf("jsonl output %q missing rejected status", jsonl.String())
|
|
}
|
|
}
|
|
|
|
func TestRunCheckReturnsRejectedWhenAnyScheduleIsRejected(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "schedule.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
schedules:
|
|
- name: unsupported
|
|
provider: kis
|
|
selector:
|
|
kind: watchlist
|
|
market: KR
|
|
venue: KRX
|
|
name: kr-core
|
|
symbols: ["005930"]
|
|
timeframe: minute_1
|
|
cadence: 24h
|
|
timezone: Asia/Seoul
|
|
backfill_window: 48h
|
|
parallelism_limit: 1
|
|
`), 0644); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
code := RunCheck([]string{"--file", path, "--output", "jsonl", "--at", "2026-06-21T10:30:00Z"}, &stdout, &stderr, func() time.Time {
|
|
return time.Date(2026, 6, 21, 10, 30, 0, 0, time.UTC)
|
|
}, CapabilityMap(testCapability()))
|
|
|
|
if code != exitRejected {
|
|
t.Fatalf("exit code = %d, want %d (stderr=%q stdout=%q)", code, exitRejected, stderr.String(), stdout.String())
|
|
}
|
|
if !strings.Contains(stdout.String(), `"status":"rejected"`) {
|
|
t.Errorf("stdout %q missing rejected JSONL", stdout.String())
|
|
}
|
|
}
|