package sqlite import ( "context" "os" "path/filepath" "testing" "stock-analysis/internal/model" ) func tempDB(t *testing.T) string { t.Helper() return filepath.Join(t.TempDir(), "test.db") } func TestOpenAndClose(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } if err := store.Close(); err != nil { t.Fatalf("close: %v", err) } } func TestSaveAndGetKlines(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() klines := []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99, Volume: 1000, Amount: 100000, ConvertPrice: 10, ConvertValue: 1010, PremiumRate: 5}, {Date: "2020-01-03", Open: 101, Close: 102, High: 103, Low: 100, Volume: 2000, Amount: 200000, ConvertPrice: 10, ConvertValue: 1020, PremiumRate: 4}, {Date: "2020-01-06", Open: 102, Close: 100, High: 104, Low: 99, Volume: 1500, Amount: 150000, ConvertPrice: 10, ConvertValue: 1000, PremiumRate: 6}, } if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("save: %v", err) } got, err := store.GetKlines(ctx, "113001") if err != nil { t.Fatalf("get: %v", err) } if len(got) != 3 { t.Fatalf("expected 3 klines, got %d", len(got)) } // 验证按日期升序 if got[0].Date != "2020-01-02" { t.Errorf("first date: expected 2020-01-02, got %s", got[0].Date) } if got[2].Date != "2020-01-06" { t.Errorf("last date: expected 2020-01-06, got %s", got[2].Date) } if got[1].Close != 102 { t.Errorf("second close: expected 102, got %f", got[1].Close) } } func TestSaveKlines_Idempotent(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() klines := []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99, Volume: 1000}, } // 第一次写入 if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("first save: %v", err) } // 第二次写入(幂等) if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("second save: %v", err) } got, err := store.GetKlines(ctx, "113001") if err != nil { t.Fatalf("get: %v", err) } if len(got) != 1 { t.Fatalf("expected 1 kline, got %d", len(got)) } } func TestCountKlines(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() // 空库 n, err := store.CountKlines(ctx, "113001") if err != nil { t.Fatalf("count empty: %v", err) } if n != 0 { t.Errorf("expected 0, got %d", n) } klines := []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99}, {Date: "2020-01-03", Open: 101, Close: 102, High: 103, Low: 100}, } if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("save: %v", err) } n, err = store.CountKlines(ctx, "113001") if err != nil { t.Fatalf("count: %v", err) } if n != 2 { t.Errorf("expected 2, got %d", n) } } func TestListCodes(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() klines := []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99}, } if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("save 113001: %v", err) } if err := store.SaveKlines(ctx, "113002", klines); err != nil { t.Fatalf("save 113002: %v", err) } codes, err := store.ListCodes(ctx) if err != nil { t.Fatalf("list: %v", err) } if len(codes) != 2 { t.Fatalf("expected 2 codes, got %d", len(codes)) } if codes[0] != "113001" || codes[1] != "113002" { t.Errorf("expected [113001, 113002], got %v", codes) } } func TestLoadAllKlines(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() if err := store.SaveKlines(ctx, "A", []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99}, {Date: "2020-01-03", Open: 101, Close: 102, High: 103, Low: 100}, }); err != nil { t.Fatalf("save A: %v", err) } if err := store.SaveKlines(ctx, "B", []model.Kline{ {Date: "2020-01-02", Open: 200, Close: 201, High: 202, Low: 199}, }); err != nil { t.Fatalf("save B: %v", err) } all, err := store.LoadAllKlines(ctx) if err != nil { t.Fatalf("load all: %v", err) } if len(all) != 2 { t.Fatalf("expected 2 codes, got %d", len(all)) } if len(all["A"]) != 2 { t.Errorf("expected 2 klines for A, got %d", len(all["A"])) } if len(all["B"]) != 1 { t.Errorf("expected 1 kline for B, got %d", len(all["B"])) } } func TestOpen_NonExistentDir(t *testing.T) { path := filepath.Join(t.TempDir(), "nonexistent", "test.db") // 确保父目录存在 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir: %v", err) } store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } store.Close() } func TestSaveBonds(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() bonds := []model.ConvertibleBond{ {Code: "113001", Name: "测试转债1", StockCode: "600001", InitialConvertPrice: 10, ListDate: "2020-01-01"}, {Code: "113002", Name: "测试转债2", StockCode: "600002", InitialConvertPrice: 15, ListDate: "2020-06-01"}, } if err := store.SaveBonds(ctx, bonds); err != nil { t.Fatalf("save bonds: %v", err) } got, err := store.ListBonds(ctx) if err != nil { t.Fatalf("list bonds: %v", err) } if len(got) != 2 { t.Fatalf("expected 2 bonds, got %d", len(got)) } if got[0].Name != "测试转债1" { t.Errorf("first bond name: expected 测试转债1, got %s", got[0].Name) } } func TestUpdatePremiumBatch(t *testing.T) { path := tempDB(t) store, err := Open(path) if err != nil { t.Fatalf("open: %v", err) } defer store.Close() ctx := context.Background() // 先写入K线 klines := []model.Kline{ {Date: "2020-01-02", Open: 100, Close: 101, High: 102, Low: 99}, } if err := store.SaveKlines(ctx, "113001", klines); err != nil { t.Fatalf("save: %v", err) } // 更新溢价率 rows := []PremiumRow{ {Date: "2020-01-02", ConvertPrice: 10, ConvertValue: 1010, PremiumRate: 5.5}, } if err := store.UpdatePremiumBatch(ctx, "113001", rows); err != nil { t.Fatalf("update premium: %v", err) } // 验证 got, err := store.GetKlines(ctx, "113001") if err != nil { t.Fatalf("get: %v", err) } if got[0].ConvertPrice != 10 { t.Errorf("convert price: expected 10, got %f", got[0].ConvertPrice) } if got[0].ConvertValue != 1010 { t.Errorf("convert value: expected 1010, got %f", got[0].ConvertValue) } if got[0].PremiumRate != 5.5 { t.Errorf("premium rate: expected 5.5, got %f", got[0].PremiumRate) } }