File size: 7,248 Bytes
5448d8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**

 * Integration tests for models.dev sync: live fetch β†’ DB save β†’ retrieve β†’ resolution order.

 *

 * Uses a test DB file in /tmp to avoid touching production data.

 */

import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { unlinkSync, existsSync } from "node:fs";

// Point DB at a temp file
const TEST_DB = "/tmp/omniroute-test-modelsdev.sqlite";

before(() => {
  if (existsSync(TEST_DB)) unlinkSync(TEST_DB);
  process.env.DATA_DIR = "/tmp";
  process.env.DB_FILE = TEST_DB;
});

after(() => {
  if (existsSync(TEST_DB)) unlinkSync(TEST_DB);
});

describe("modelsDevSync β€” integration: live fetch β†’ DB β†’ retrieve", () => {
  it("fetches, saves pricing, and retrieves from DB", async () => {
    const {
      syncModelsDev,
      getModelsDevPricing,
      getSyncedCapabilities,
      clearModelsDevPricing,
      clearModelsDevCapabilities,
    } = await import("../../src/lib/modelsDevSync.ts");

    // 1. Sync from live API
    const result = await syncModelsDev({ dryRun: false, syncCapabilities: true });
    assert.equal(result.success, true, `sync should succeed: ${result.error || ""}`);
    assert.ok(result.modelCount > 0, `should have pricing entries, got ${result.modelCount}`);
    assert.ok(result.providerCount > 0, `should have providers, got ${result.providerCount}`);
    assert.ok(
      result.capabilityCount > 0,
      `should have capabilities, got ${result.capabilityCount}`
    );

    // 2. Retrieve pricing from DB
    const pricing = getModelsDevPricing();
    assert.ok(Object.keys(pricing).length > 0, "should have pricing in DB");

    // 3. Verify specific known models
    assert.ok(pricing.openai, "openai pricing should exist");
    assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist in openai");
    assert.ok(pricing.openai["gpt-4o"].input > 0, "gpt-4o input price should be > 0");
    assert.ok(pricing.openai["gpt-4o"].output > 0, "gpt-4o output price should be > 0");

    assert.ok(pricing.anthropic, "anthropic pricing should exist");
    const claudeModels = Object.keys(pricing.anthropic).filter((m) => m.includes("claude"));
    assert.ok(claudeModels.length > 0, "should have claude pricing entries");

    assert.ok(pricing.deepseek, "deepseek pricing should exist");
    assert.ok(pricing.if, "if (Qoder) pricing should exist (mapped from deepseek)");

    // 4. Retrieve capabilities from DB
    const caps = getSyncedCapabilities();
    assert.ok(Object.keys(caps).length > 0, "should have capabilities in DB");

    assert.ok(caps.openai, "openai capabilities should exist");
    assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist");
    assert.equal(caps.openai["gpt-4o"].tool_call, true);
    assert.equal(caps.openai["gpt-4o"].reasoning, false);
    assert.equal(caps.openai["gpt-4o"].attachment, true);

    // 5. Query specific capability
    const specificCaps = getSyncedCapabilities("openai", "gpt-4o");
    assert.ok(specificCaps.openai, "specific query should return openai");
    assert.ok(specificCaps.openai["gpt-4o"], "specific query should return gpt-4o");
    assert.equal(specificCaps.openai["gpt-4o"].limit_context, 128000);

    // 6. Cleanup
    clearModelsDevPricing();
    clearModelsDevCapabilities();

    const afterPricing = getModelsDevPricing();
    assert.equal(Object.keys(afterPricing).length, 0, "pricing should be cleared");

    const afterCaps = getSyncedCapabilities();
    assert.equal(Object.keys(afterCaps).length, 0, "capabilities should be cleared");
  });

  it("dryRun returns data without saving to DB", async () => {
    const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } =
      await import("../../src/lib/modelsDevSync.ts");

    // Clear any existing data first
    clearModelsDevPricing();

    const result = await syncModelsDev({ dryRun: true, syncCapabilities: false });
    assert.equal(result.success, true);
    assert.ok(result.data, "dryRun should return data");
    assert.ok(result.data.pricing, "dryRun should return pricing data");

    // Verify nothing was saved
    const pricing = getModelsDevPricing();
    assert.equal(Object.keys(pricing).length, 0, "dryRun should not save to DB");
  });

  it("sync without capabilities only saves pricing", async () => {
    const {
      syncModelsDev,
      getModelsDevPricing,
      getSyncedCapabilities,
      clearModelsDevPricing,
      clearModelsDevCapabilities,
    } = await import("../../src/lib/modelsDevSync.ts");

    clearModelsDevPricing();
    clearModelsDevCapabilities();

    const result = await syncModelsDev({ dryRun: false, syncCapabilities: false });
    assert.equal(result.success, true);
    assert.ok(result.modelCount > 0, "should have pricing entries");
    assert.equal(result.capabilityCount, 0, "capabilityCount should be 0 when disabled");

    // Pricing should be saved
    const pricing = getModelsDevPricing();
    assert.ok(Object.keys(pricing).length > 0, "pricing should be saved");

    // Capabilities should NOT be saved
    const caps = getSyncedCapabilities();
    assert.equal(Object.keys(caps).length, 0, "capabilities should not be saved");

    clearModelsDevPricing();
  });
});

describe("modelsDevSync β€” resolution order: user > models.dev > LiteLLM > default", () => {
  it("returns models.dev pricing when no user override exists", async () => {
    const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } =
      await import("../../src/lib/modelsDevSync.ts");

    clearModelsDevPricing();
    await syncModelsDev({ dryRun: false, syncCapabilities: false });

    const pricing = getModelsDevPricing();
    assert.ok(pricing.openai, "openai pricing should exist");
    assert.ok(pricing.openai["gpt-4o"], "gpt-4o should have models.dev pricing");

    clearModelsDevPricing();
  });

  it("getPricing() merges all layers correctly", async () => {
    const { getPricing } = await import("../../src/lib/db/settings.ts");
    const { syncModelsDev, clearModelsDevPricing, saveModelsDevPricing } =
      await import("../../src/lib/modelsDevSync.ts");
    const { updatePricing, resetPricing } = await import("../../src/lib/db/settings.ts");

    // Clear all synced data first
    clearModelsDevPricing();

    // Sync models.dev pricing
    await syncModelsDev({ dryRun: false, syncCapabilities: false });

    // Get merged pricing
    const merged = await getPricing();

    // Should have openai pricing (either from models.dev or hardcoded defaults)
    assert.ok(merged.openai || merged.cx, "should have openai/cx pricing in merged result");

    // Add a user override
    await updatePricing({
      openai: {
        "gpt-4o": { input: 999, output: 999 },
      },
    });

    // Verify user override wins
    const mergedWithOverride = await getPricing();
    assert.equal(
      mergedWithOverride.openai?.["gpt-4o"]?.input,
      999,
      "user override should win over models.dev"
    );

    // Reset the override
    await resetPricing("openai", "gpt-4o");
    clearModelsDevPricing();
  });
});