ledger-consistency.test.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { sql } from "drizzle-orm";
  2. import { describe, expect, it } from "vitest";
  3. import { db } from "@/drizzle/db";
  4. if (!process.env.DATABASE_URL && process.env.DSN) {
  5. process.env.DATABASE_URL = process.env.DSN;
  6. }
  7. function requireSingleRow<T>(result: Iterable<unknown>): T {
  8. const row = Array.from(result)[0] as T | undefined;
  9. if (!row) {
  10. throw new Error("expected query to return one row");
  11. }
  12. return row;
  13. }
  14. describe.skipIf(!process.env.DATABASE_URL)("Ledger data consistency", () => {
  15. it("warmup rows never appear in usage_ledger", async () => {
  16. const result = await db.execute(sql`
  17. SELECT COUNT(*)::integer AS warmup_count
  18. FROM usage_ledger
  19. WHERE blocked_by = 'warmup'
  20. `);
  21. const row = requireSingleRow<{ warmup_count: number }>(result);
  22. expect(row.warmup_count).toBe(0);
  23. });
  24. it("all non-warmup message_request rows have ledger entries", async () => {
  25. const result = await db.execute(sql`
  26. SELECT
  27. (
  28. SELECT COUNT(*)::integer
  29. FROM message_request
  30. WHERE blocked_by IS DISTINCT FROM 'warmup'
  31. ) AS message_request_count,
  32. (
  33. SELECT COUNT(*)::integer
  34. FROM usage_ledger
  35. ) AS usage_ledger_count
  36. `);
  37. const row = requireSingleRow<{
  38. message_request_count: number;
  39. usage_ledger_count: number;
  40. }>(result);
  41. expect(row.usage_ledger_count).toBe(row.message_request_count);
  42. });
  43. it("cost aggregation matches between tables", async () => {
  44. const result = await db.execute(sql`
  45. WITH bounds AS (
  46. SELECT
  47. COALESCE(MIN(created_at), CURRENT_TIMESTAMP) AS start_at,
  48. COALESCE(MAX(created_at) + INTERVAL '1 millisecond', CURRENT_TIMESTAMP) AS end_at
  49. FROM message_request
  50. WHERE blocked_by IS DISTINCT FROM 'warmup'
  51. ),
  52. message_sum AS (
  53. SELECT COALESCE(SUM(mr.cost_usd), 0) AS total_cost
  54. FROM message_request mr
  55. CROSS JOIN bounds b
  56. WHERE mr.blocked_by IS DISTINCT FROM 'warmup'
  57. AND mr.created_at >= b.start_at
  58. AND mr.created_at < b.end_at
  59. ),
  60. ledger_sum AS (
  61. SELECT COALESCE(SUM(ul.cost_usd), 0) AS total_cost
  62. FROM usage_ledger ul
  63. CROSS JOIN bounds b
  64. WHERE ul.created_at >= b.start_at
  65. AND ul.created_at < b.end_at
  66. )
  67. SELECT
  68. message_sum.total_cost::text AS message_total_cost,
  69. ledger_sum.total_cost::text AS ledger_total_cost,
  70. (message_sum.total_cost = ledger_sum.total_cost) AS is_equal
  71. FROM message_sum, ledger_sum
  72. `);
  73. const row = requireSingleRow<{
  74. message_total_cost: string;
  75. ledger_total_cost: string;
  76. is_equal: boolean;
  77. }>(result);
  78. expect(row.is_equal).toBe(true);
  79. });
  80. it("provider attribution uses finalProviderId", async () => {
  81. const result = await db.execute(sql`
  82. WITH candidates AS (
  83. SELECT
  84. mr.id,
  85. mr.provider_id,
  86. jsonb_array_length(mr.provider_chain) AS chain_length,
  87. (mr.provider_chain -> -1 ->> 'id')::integer AS expected_final_provider_id
  88. FROM message_request mr
  89. WHERE mr.provider_chain IS NOT NULL
  90. AND jsonb_typeof(mr.provider_chain) = 'array'
  91. AND jsonb_array_length(mr.provider_chain) > 0
  92. AND jsonb_typeof(mr.provider_chain -> -1) = 'object'
  93. AND (mr.provider_chain -> -1 ? 'id')
  94. AND (mr.provider_chain -> -1 ->> 'id') ~ '^[0-9]+$'
  95. )
  96. SELECT
  97. COUNT(*)::integer AS candidate_count,
  98. COUNT(*) FILTER (
  99. WHERE ul.final_provider_id <> c.expected_final_provider_id
  100. )::integer AS wrong_final_provider_count,
  101. COUNT(*) FILTER (
  102. WHERE c.chain_length > 1
  103. AND c.expected_final_provider_id <> c.provider_id
  104. AND ul.final_provider_id = ul.provider_id
  105. )::integer AS not_using_final_provider_count
  106. FROM candidates c
  107. JOIN usage_ledger ul ON ul.request_id = c.id
  108. `);
  109. const row = requireSingleRow<{
  110. candidate_count: number;
  111. wrong_final_provider_count: number;
  112. not_using_final_provider_count: number;
  113. }>(result);
  114. expect(row.wrong_final_provider_count).toBe(0);
  115. expect(row.not_using_final_provider_count).toBe(0);
  116. });
  117. it("is_success matches error_message IS NULL", async () => {
  118. const result = await db.execute(sql`
  119. SELECT
  120. COUNT(*)::integer AS checked_count,
  121. COUNT(*) FILTER (
  122. WHERE ul.is_success IS DISTINCT FROM (mr.error_message IS NULL OR mr.error_message = '')
  123. )::integer AS mismatch_count
  124. FROM message_request mr
  125. JOIN usage_ledger ul ON ul.request_id = mr.id
  126. WHERE mr.blocked_by IS DISTINCT FROM 'warmup'
  127. `);
  128. const row = requireSingleRow<{ checked_count: number; mismatch_count: number }>(result);
  129. expect(row.mismatch_count).toBe(0);
  130. });
  131. });