|
1 | 1 | import { describe, it } from "@std/testing/bdd"; |
2 | 2 | import { expect } from "@std/expect"; |
| 3 | +import { ID } from "@commontools/builder"; |
| 4 | +import { Identity } from "@commontools/identity"; |
3 | 5 | import { storage } from "../src/storage.ts"; |
4 | | -import { getDoc } from "@commontools/runner"; |
| 6 | +import { getDoc } from "../src/doc.ts"; |
| 7 | +import { isCellLink } from "../src/cell.ts"; |
5 | 8 | import { VolatileStorageProvider } from "../src/storage/volatile.ts"; |
6 | | -import { Identity } from "@commontools/identity"; |
7 | 9 |
|
8 | 10 | storage.setRemoteStorage(new URL(`volatile:`)); |
9 | 11 | storage.setSigner(await Identity.fromPassphrase("test operator")); |
@@ -51,7 +53,7 @@ describe("Push conflict", () => { |
51 | 53 | "name", |
52 | 54 | "push and set", |
53 | 55 | ); |
54 | | - const listDoc = getDoc<any[]>([], "list", "push and set"); |
| 56 | + const listDoc = getDoc<any[]>([], "list 2", "push and set"); |
55 | 57 |
|
56 | 58 | const name = nameDoc.asCell(); |
57 | 59 | const list = listDoc.asCell(); |
@@ -96,4 +98,64 @@ describe("Push conflict", () => { |
96 | 98 | // Retry list should be empty now, since the change was applied. |
97 | 99 | expect(!!listDoc.retry?.length).toBe(false); |
98 | 100 | }); |
| 101 | + |
| 102 | + it("should resolve push conflicts with ID among other conflicts", async () => { |
| 103 | + const nameDoc = getDoc<string | undefined>( |
| 104 | + undefined, |
| 105 | + "name 2", |
| 106 | + "push and set", |
| 107 | + ); |
| 108 | + const listDoc = getDoc<any[]>([], "list 3", "push and set"); |
| 109 | + |
| 110 | + const name = nameDoc.asCell(); |
| 111 | + const list = listDoc.asCell(); |
| 112 | + |
| 113 | + await storage.syncCell(name); |
| 114 | + await storage.syncCell(list); |
| 115 | + |
| 116 | + const memory = new VolatileStorageProvider("push and set"); |
| 117 | + |
| 118 | + // Update memory without notifying main storage |
| 119 | + await memory.sync(nameDoc.entityId, true); // Get current value |
| 120 | + await memory.sync(listDoc.entityId, true); // Get current value |
| 121 | + await memory.send<any>([{ |
| 122 | + entityId: nameDoc.entityId, |
| 123 | + value: { value: "foo" }, |
| 124 | + }, { |
| 125 | + entityId: listDoc.entityId, |
| 126 | + value: { value: [{ n: 1 }, { n: 2 }, { n: 3 }] }, |
| 127 | + }], true); // true = do not notify main storage |
| 128 | + |
| 129 | + let retryCalled = 0; |
| 130 | + listDoc.retry = [(value) => { |
| 131 | + retryCalled++; |
| 132 | + return value; |
| 133 | + }]; |
| 134 | + |
| 135 | + name.set("bar"); |
| 136 | + list.push({ n: 4, [ID]: "4" }); |
| 137 | + |
| 138 | + // This is locally ahead of the db, and retry wasn't called yet. |
| 139 | + expect(name.get()).toEqual("bar"); |
| 140 | + expect(list.get()).toEqual([{ n: 4 }]); |
| 141 | + expect(isCellLink(listDoc.get()?.[0])).toBe(true); |
| 142 | + const entry = listDoc.get()[0].cell?.asCell(); |
| 143 | + expect(retryCalled).toEqual(0); |
| 144 | + |
| 145 | + await storage.synced(); |
| 146 | + |
| 147 | + // We successfully replayed the change on top of the db: |
| 148 | + expect(name.get()).toEqual("foo"); |
| 149 | + expect( |
| 150 | + list.asSchema({ |
| 151 | + type: "array", |
| 152 | + items: { type: "object", properties: { n: { type: "number" } } }, |
| 153 | + }).get(), |
| 154 | + ).toEqual([{ n: 1 }, { n: 2 }, { n: 3 }, { n: 4 }]); |
| 155 | + expect(retryCalled).toEqual(1); |
| 156 | + expect(!!listDoc.retry?.length).toBe(false); |
| 157 | + |
| 158 | + // Check that the ID is still there |
| 159 | + expect(entry.equals(listDoc.get()[3])).toBe(true); |
| 160 | + }); |
99 | 161 | }); |
0 commit comments