@fedify/testing 2.4.0-dev.1904 → 2.4.0-pr.1020.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/mod.cjs +146 -3
- package/dist/mod.d.cts +49 -2
- package/dist/mod.d.ts +49 -2
- package/dist/mod.js +142 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -95,6 +95,33 @@ The package also exports helper functions for creating various context types:
|
|
|
95
95
|
- `createRequestContext()`: Creates a request context
|
|
96
96
|
- `createInboxContext()`: Creates an inbox context
|
|
97
97
|
|
|
98
|
+
### Store conformance tests
|
|
99
|
+
|
|
100
|
+
The package provides reusable conformance suites for Fedify storage adapters.
|
|
101
|
+
Use `testKvStore()` to check a `KvStore` implementation and
|
|
102
|
+
`testMessageQueue()` to check a `MessageQueue` implementation:
|
|
103
|
+
|
|
104
|
+
~~~~ typescript
|
|
105
|
+
import { test } from "@fedify/fixture";
|
|
106
|
+
import { testKvStore, testMessageQueue } from "@fedify/testing";
|
|
107
|
+
|
|
108
|
+
test("MyKvStore", () =>
|
|
109
|
+
testKvStore(
|
|
110
|
+
() => new MyKvStore(),
|
|
111
|
+
async ({ store }) => await store.close(),
|
|
112
|
+
));
|
|
113
|
+
|
|
114
|
+
test("MyMessageQueue", () =>
|
|
115
|
+
testMessageQueue(
|
|
116
|
+
() => new MyMessageQueue(),
|
|
117
|
+
async ({ mq1, mq2, controller }) => {
|
|
118
|
+
controller.abort();
|
|
119
|
+
await mq1.close();
|
|
120
|
+
await mq2.close();
|
|
121
|
+
},
|
|
122
|
+
));
|
|
123
|
+
~~~~
|
|
124
|
+
|
|
98
125
|
|
|
99
126
|
Features
|
|
100
127
|
--------
|
package/dist/mod.cjs
CHANGED
|
@@ -4523,7 +4523,7 @@ const Now = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
4523
4523
|
}
|
|
4524
4524
|
})
|
|
4525
4525
|
});
|
|
4526
|
-
const Temporal$
|
|
4526
|
+
const Temporal$3 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
4527
4527
|
...createStringTagDescriptors("Temporal"),
|
|
4528
4528
|
...createPropDescriptors({
|
|
4529
4529
|
PlainYearMonth,
|
|
@@ -4539,11 +4539,153 @@ const Temporal$2 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
4539
4539
|
});
|
|
4540
4540
|
//#endregion
|
|
4541
4541
|
//#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/index.js
|
|
4542
|
-
const Temporal$
|
|
4542
|
+
const Temporal$2 = NativeTemporal || Temporal$3;
|
|
4543
4543
|
NativeTemporal && Date.prototype.toTemporalInstant;
|
|
4544
4544
|
//#endregion
|
|
4545
|
+
//#region src/kv-tester.ts
|
|
4546
|
+
const Temporal$1 = Temporal$2;
|
|
4547
|
+
/**
|
|
4548
|
+
* Tests a {@link KvStore} implementation with a standard set of tests.
|
|
4549
|
+
*
|
|
4550
|
+
* The suite covers reads and writes, deletion, structured keys and values,
|
|
4551
|
+
* prefix listing, TTL expiration, and optional compare-and-swap behavior.
|
|
4552
|
+
* Every key is isolated below a random prefix and is deleted after the suite.
|
|
4553
|
+
*
|
|
4554
|
+
* @example
|
|
4555
|
+
* ```typescript ignore
|
|
4556
|
+
* import { test } from "@fedify/fixture";
|
|
4557
|
+
* import { testKvStore } from "@fedify/testing";
|
|
4558
|
+
* import { MyKvStore } from "./my-kv.ts";
|
|
4559
|
+
*
|
|
4560
|
+
* test("MyKvStore", () =>
|
|
4561
|
+
* testKvStore(
|
|
4562
|
+
* () => new MyKvStore(),
|
|
4563
|
+
* async ({ store }) => await store.close(),
|
|
4564
|
+
* )
|
|
4565
|
+
* );
|
|
4566
|
+
* ```
|
|
4567
|
+
*
|
|
4568
|
+
* @param getKvStore A factory that creates the store to test.
|
|
4569
|
+
* @param onFinally A cleanup function called after the suite completes.
|
|
4570
|
+
* @param options Optional configuration for the suite.
|
|
4571
|
+
*/
|
|
4572
|
+
async function testKvStore(getKvStore, onFinally, options = {}) {
|
|
4573
|
+
const store = await getKvStore();
|
|
4574
|
+
const prefix = [`fedify_test_${crypto.randomUUID()}`];
|
|
4575
|
+
const key = (...segments) => [...prefix, ...segments];
|
|
4576
|
+
try {
|
|
4577
|
+
(0, node_assert_strict.strictEqual)(await store.get(key("missing")), void 0);
|
|
4578
|
+
for (const [index, value] of [
|
|
4579
|
+
"text",
|
|
4580
|
+
42,
|
|
4581
|
+
true,
|
|
4582
|
+
null,
|
|
4583
|
+
[
|
|
4584
|
+
1,
|
|
4585
|
+
"two",
|
|
4586
|
+
false
|
|
4587
|
+
],
|
|
4588
|
+
{ nested: { value: "test" } }
|
|
4589
|
+
].entries()) {
|
|
4590
|
+
const valueKey = key("values", index.toString());
|
|
4591
|
+
await store.set(valueKey, value);
|
|
4592
|
+
(0, node_assert_strict.deepStrictEqual)(await store.get(valueKey), value);
|
|
4593
|
+
}
|
|
4594
|
+
const overwriteKey = key("overwrite");
|
|
4595
|
+
await store.set(overwriteKey, "before");
|
|
4596
|
+
await store.set(overwriteKey, "after");
|
|
4597
|
+
(0, node_assert_strict.strictEqual)(await store.get(overwriteKey), "after");
|
|
4598
|
+
const deleteKey = key("delete");
|
|
4599
|
+
await store.set(deleteKey, "value");
|
|
4600
|
+
await store.delete(deleteKey);
|
|
4601
|
+
(0, node_assert_strict.strictEqual)(await store.get(deleteKey), void 0);
|
|
4602
|
+
await store.delete(deleteKey);
|
|
4603
|
+
const isolatedKeys = [
|
|
4604
|
+
key("a", "b"),
|
|
4605
|
+
key("a,b"),
|
|
4606
|
+
key("a:b")
|
|
4607
|
+
];
|
|
4608
|
+
for (const [index, isolatedKey] of isolatedKeys.entries()) await store.set(isolatedKey, index);
|
|
4609
|
+
for (const [index, isolatedKey] of isolatedKeys.entries()) (0, node_assert_strict.strictEqual)(await store.get(isolatedKey), index);
|
|
4610
|
+
if (options.testTtl ?? true) {
|
|
4611
|
+
const expiringKey = key("ttl", "expiring");
|
|
4612
|
+
await store.set(expiringKey, "expired", { ttl: Temporal$1.Duration.from({ milliseconds: 50 }) });
|
|
4613
|
+
await (0, es_toolkit.delay)(100);
|
|
4614
|
+
(0, node_assert_strict.strictEqual)(await store.get(expiringKey), void 0);
|
|
4615
|
+
(0, node_assert_strict.deepStrictEqual)(await collect(store.list(key("ttl"))), []);
|
|
4616
|
+
const refreshedKey = key("ttl", "refreshed");
|
|
4617
|
+
await store.set(refreshedKey, "temporary", { ttl: Temporal$1.Duration.from({ milliseconds: 50 }) });
|
|
4618
|
+
await store.set(refreshedKey, "persistent");
|
|
4619
|
+
await (0, es_toolkit.delay)(100);
|
|
4620
|
+
(0, node_assert_strict.strictEqual)(await store.get(refreshedKey), "persistent");
|
|
4621
|
+
}
|
|
4622
|
+
const listPrefix = key("list");
|
|
4623
|
+
const listEntries = [
|
|
4624
|
+
{
|
|
4625
|
+
key: listPrefix,
|
|
4626
|
+
value: "exact"
|
|
4627
|
+
},
|
|
4628
|
+
{
|
|
4629
|
+
key: key("list", "child"),
|
|
4630
|
+
value: "child"
|
|
4631
|
+
},
|
|
4632
|
+
{
|
|
4633
|
+
key: key("list", "child", "nested"),
|
|
4634
|
+
value: "nested"
|
|
4635
|
+
},
|
|
4636
|
+
{
|
|
4637
|
+
key: key("list-other"),
|
|
4638
|
+
value: "other"
|
|
4639
|
+
}
|
|
4640
|
+
];
|
|
4641
|
+
for (const entry of listEntries) await store.set(entry.key, entry.value);
|
|
4642
|
+
(0, node_assert_strict.deepStrictEqual)(normalize(await collect(store.list(listPrefix))), normalize(listEntries.slice(0, 3)));
|
|
4643
|
+
const prefixedEntries = await collect(store.list(prefix));
|
|
4644
|
+
(0, node_assert_strict.deepStrictEqual)(normalize(filterByPrefix(await collect(store.list()), prefix)), normalize(prefixedEntries));
|
|
4645
|
+
(0, node_assert_strict.deepStrictEqual)(normalize(filterByPrefix(await collect(store.list([])), prefix)), normalize(prefixedEntries));
|
|
4646
|
+
if (options.testCas ?? store.cas != null) {
|
|
4647
|
+
if (store.cas == null) throw new TypeError("KvStore.cas is not implemented");
|
|
4648
|
+
const cas = store.cas.bind(store);
|
|
4649
|
+
const casKey = key("cas", "value");
|
|
4650
|
+
(0, node_assert_strict.strictEqual)(await cas(casKey, "wrong", "value"), false);
|
|
4651
|
+
(0, node_assert_strict.strictEqual)(await cas(casKey, void 0, "created"), true);
|
|
4652
|
+
(0, node_assert_strict.strictEqual)(await cas(casKey, "wrong", "updated"), false);
|
|
4653
|
+
(0, node_assert_strict.strictEqual)(await cas(casKey, "created", "updated"), true);
|
|
4654
|
+
(0, node_assert_strict.strictEqual)(await store.get(casKey), "updated");
|
|
4655
|
+
(0, node_assert_strict.strictEqual)(await cas(casKey, "updated", void 0), true);
|
|
4656
|
+
(0, node_assert_strict.strictEqual)(await store.get(casKey), void 0);
|
|
4657
|
+
if (options.testTtl ?? true) {
|
|
4658
|
+
const expiredCasKey = key("cas", "expired");
|
|
4659
|
+
await store.set(expiredCasKey, "old", { ttl: Temporal$1.Duration.from({ milliseconds: 50 }) });
|
|
4660
|
+
await (0, es_toolkit.delay)(100);
|
|
4661
|
+
(0, node_assert_strict.strictEqual)(await cas(expiredCasKey, void 0, "replaced"), true);
|
|
4662
|
+
const ttlCasKey = key("cas", "ttl");
|
|
4663
|
+
(0, node_assert_strict.strictEqual)(await cas(ttlCasKey, void 0, "temporary", { ttl: Temporal$1.Duration.from({ milliseconds: 50 }) }), true);
|
|
4664
|
+
await (0, es_toolkit.delay)(100);
|
|
4665
|
+
(0, node_assert_strict.strictEqual)(await store.get(ttlCasKey), void 0);
|
|
4666
|
+
}
|
|
4667
|
+
const concurrentKey = key("cas", "concurrent");
|
|
4668
|
+
(0, node_assert_strict.strictEqual)((await Promise.all(Array.from({ length: 8 }, (_, index) => cas(concurrentKey, void 0, index)))).filter(Boolean).length, 1);
|
|
4669
|
+
}
|
|
4670
|
+
} finally {
|
|
4671
|
+
for await (const entry of store.list(prefix)) await store.delete(entry.key);
|
|
4672
|
+
await onFinally({ store });
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
async function collect(entries) {
|
|
4676
|
+
const results = [];
|
|
4677
|
+
for await (const entry of entries) results.push(entry);
|
|
4678
|
+
return results;
|
|
4679
|
+
}
|
|
4680
|
+
function normalize(entries) {
|
|
4681
|
+
return entries.map((entry) => JSON.stringify(entry)).sort();
|
|
4682
|
+
}
|
|
4683
|
+
function filterByPrefix(entries, prefix) {
|
|
4684
|
+
return entries.filter((entry) => prefix.every((segment, index) => entry.key[index] === segment));
|
|
4685
|
+
}
|
|
4686
|
+
//#endregion
|
|
4545
4687
|
//#region src/mq-tester.ts
|
|
4546
|
-
const Temporal = Temporal$
|
|
4688
|
+
const Temporal = Temporal$2;
|
|
4547
4689
|
/**
|
|
4548
4690
|
* Tests a {@link MessageQueue} implementation with a standard set of tests.
|
|
4549
4691
|
*
|
|
@@ -4729,5 +4871,6 @@ exports.createInboxContext = createInboxContext;
|
|
|
4729
4871
|
exports.createOutboxContext = createOutboxContext;
|
|
4730
4872
|
exports.createRequestContext = createRequestContext;
|
|
4731
4873
|
exports.getRandomKey = getRandomKey;
|
|
4874
|
+
exports.testKvStore = testKvStore;
|
|
4732
4875
|
exports.testMessageQueue = testMessageQueue;
|
|
4733
4876
|
exports.waitFor = waitFor;
|
package/dist/mod.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Context, Federation, InboxContext, OutboxContext, RequestContext } from "@fedify/fedify/federation";
|
|
3
3
|
import { Activity } from "@fedify/vocab";
|
|
4
|
-
import { MessageQueue } from "@fedify/fedify";
|
|
4
|
+
import { KvStore, MessageQueue } from "@fedify/fedify";
|
|
5
5
|
|
|
6
6
|
//#region src/context.d.ts
|
|
7
7
|
declare function createContext<TContextData>(values: Partial<Context<TContextData>> & {
|
|
@@ -153,6 +153,53 @@ declare function createFederation<TContextData>(options?: {
|
|
|
153
153
|
tracerProvider?: any;
|
|
154
154
|
}): TestFederation<TContextData>;
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region src/kv-tester.d.ts
|
|
157
|
+
/**
|
|
158
|
+
* Options for {@link testKvStore}.
|
|
159
|
+
*/
|
|
160
|
+
interface TestKvStoreOptions {
|
|
161
|
+
/**
|
|
162
|
+
* Whether to test compare-and-swap support. By default, these tests run
|
|
163
|
+
* when the store implements {@link KvStore.cas}.
|
|
164
|
+
*/
|
|
165
|
+
readonly testCas?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether to test time-to-live support. `true` by default.
|
|
168
|
+
* @default true
|
|
169
|
+
*/
|
|
170
|
+
readonly testTtl?: boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Tests a {@link KvStore} implementation with a standard set of tests.
|
|
174
|
+
*
|
|
175
|
+
* The suite covers reads and writes, deletion, structured keys and values,
|
|
176
|
+
* prefix listing, TTL expiration, and optional compare-and-swap behavior.
|
|
177
|
+
* Every key is isolated below a random prefix and is deleted after the suite.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript ignore
|
|
181
|
+
* import { test } from "@fedify/fixture";
|
|
182
|
+
* import { testKvStore } from "@fedify/testing";
|
|
183
|
+
* import { MyKvStore } from "./my-kv.ts";
|
|
184
|
+
*
|
|
185
|
+
* test("MyKvStore", () =>
|
|
186
|
+
* testKvStore(
|
|
187
|
+
* () => new MyKvStore(),
|
|
188
|
+
* async ({ store }) => await store.close(),
|
|
189
|
+
* )
|
|
190
|
+
* );
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param getKvStore A factory that creates the store to test.
|
|
194
|
+
* @param onFinally A cleanup function called after the suite completes.
|
|
195
|
+
* @param options Optional configuration for the suite.
|
|
196
|
+
*/
|
|
197
|
+
declare function testKvStore<KV extends KvStore>(getKvStore: () => KV | Promise<KV>, onFinally: ({
|
|
198
|
+
store
|
|
199
|
+
}: {
|
|
200
|
+
store: KV;
|
|
201
|
+
}) => Promise<void> | void, options?: TestKvStoreOptions): Promise<void>;
|
|
202
|
+
//#endregion
|
|
156
203
|
//#region src/mq-tester.d.ts
|
|
157
204
|
/**
|
|
158
205
|
* Options for {@link testMessageQueue}.
|
|
@@ -223,4 +270,4 @@ declare function testMessageQueue<MQ extends MessageQueue>(getMessageQueue: () =
|
|
|
223
270
|
declare function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void>;
|
|
224
271
|
declare const getRandomKey: (prefix: string) => string;
|
|
225
272
|
//#endregion
|
|
226
|
-
export { type TestMessageQueueOptions, createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
|
|
273
|
+
export { type TestKvStoreOptions, type TestMessageQueueOptions, createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testKvStore, testMessageQueue, waitFor };
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Activity } from "@fedify/vocab";
|
|
3
3
|
import { Context, Federation, InboxContext, OutboxContext, RequestContext } from "@fedify/fedify/federation";
|
|
4
|
-
import { MessageQueue } from "@fedify/fedify";
|
|
4
|
+
import { KvStore, MessageQueue } from "@fedify/fedify";
|
|
5
5
|
|
|
6
6
|
//#region src/context.d.ts
|
|
7
7
|
declare function createContext<TContextData>(values: Partial<Context<TContextData>> & {
|
|
@@ -153,6 +153,53 @@ declare function createFederation<TContextData>(options?: {
|
|
|
153
153
|
tracerProvider?: any;
|
|
154
154
|
}): TestFederation<TContextData>;
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region src/kv-tester.d.ts
|
|
157
|
+
/**
|
|
158
|
+
* Options for {@link testKvStore}.
|
|
159
|
+
*/
|
|
160
|
+
interface TestKvStoreOptions {
|
|
161
|
+
/**
|
|
162
|
+
* Whether to test compare-and-swap support. By default, these tests run
|
|
163
|
+
* when the store implements {@link KvStore.cas}.
|
|
164
|
+
*/
|
|
165
|
+
readonly testCas?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether to test time-to-live support. `true` by default.
|
|
168
|
+
* @default true
|
|
169
|
+
*/
|
|
170
|
+
readonly testTtl?: boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Tests a {@link KvStore} implementation with a standard set of tests.
|
|
174
|
+
*
|
|
175
|
+
* The suite covers reads and writes, deletion, structured keys and values,
|
|
176
|
+
* prefix listing, TTL expiration, and optional compare-and-swap behavior.
|
|
177
|
+
* Every key is isolated below a random prefix and is deleted after the suite.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript ignore
|
|
181
|
+
* import { test } from "@fedify/fixture";
|
|
182
|
+
* import { testKvStore } from "@fedify/testing";
|
|
183
|
+
* import { MyKvStore } from "./my-kv.ts";
|
|
184
|
+
*
|
|
185
|
+
* test("MyKvStore", () =>
|
|
186
|
+
* testKvStore(
|
|
187
|
+
* () => new MyKvStore(),
|
|
188
|
+
* async ({ store }) => await store.close(),
|
|
189
|
+
* )
|
|
190
|
+
* );
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param getKvStore A factory that creates the store to test.
|
|
194
|
+
* @param onFinally A cleanup function called after the suite completes.
|
|
195
|
+
* @param options Optional configuration for the suite.
|
|
196
|
+
*/
|
|
197
|
+
declare function testKvStore<KV extends KvStore>(getKvStore: () => KV | Promise<KV>, onFinally: ({
|
|
198
|
+
store
|
|
199
|
+
}: {
|
|
200
|
+
store: KV;
|
|
201
|
+
}) => Promise<void> | void, options?: TestKvStoreOptions): Promise<void>;
|
|
202
|
+
//#endregion
|
|
156
203
|
//#region src/mq-tester.d.ts
|
|
157
204
|
/**
|
|
158
205
|
* Options for {@link testMessageQueue}.
|
|
@@ -223,4 +270,4 @@ declare function testMessageQueue<MQ extends MessageQueue>(getMessageQueue: () =
|
|
|
223
270
|
declare function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void>;
|
|
224
271
|
declare const getRandomKey: (prefix: string) => string;
|
|
225
272
|
//#endregion
|
|
226
|
-
export { type TestMessageQueueOptions, createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
|
|
273
|
+
export { type TestKvStoreOptions, type TestMessageQueueOptions, createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testKvStore, testMessageQueue, waitFor };
|
package/dist/mod.js
CHANGED
|
@@ -977,6 +977,147 @@ var MockContext = class MockContext {
|
|
|
977
977
|
}
|
|
978
978
|
};
|
|
979
979
|
//#endregion
|
|
980
|
+
//#region src/kv-tester.ts
|
|
981
|
+
/**
|
|
982
|
+
* Tests a {@link KvStore} implementation with a standard set of tests.
|
|
983
|
+
*
|
|
984
|
+
* The suite covers reads and writes, deletion, structured keys and values,
|
|
985
|
+
* prefix listing, TTL expiration, and optional compare-and-swap behavior.
|
|
986
|
+
* Every key is isolated below a random prefix and is deleted after the suite.
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* ```typescript ignore
|
|
990
|
+
* import { test } from "@fedify/fixture";
|
|
991
|
+
* import { testKvStore } from "@fedify/testing";
|
|
992
|
+
* import { MyKvStore } from "./my-kv.ts";
|
|
993
|
+
*
|
|
994
|
+
* test("MyKvStore", () =>
|
|
995
|
+
* testKvStore(
|
|
996
|
+
* () => new MyKvStore(),
|
|
997
|
+
* async ({ store }) => await store.close(),
|
|
998
|
+
* )
|
|
999
|
+
* );
|
|
1000
|
+
* ```
|
|
1001
|
+
*
|
|
1002
|
+
* @param getKvStore A factory that creates the store to test.
|
|
1003
|
+
* @param onFinally A cleanup function called after the suite completes.
|
|
1004
|
+
* @param options Optional configuration for the suite.
|
|
1005
|
+
*/
|
|
1006
|
+
async function testKvStore(getKvStore, onFinally, options = {}) {
|
|
1007
|
+
const store = await getKvStore();
|
|
1008
|
+
const prefix = [`fedify_test_${crypto.randomUUID()}`];
|
|
1009
|
+
const key = (...segments) => [...prefix, ...segments];
|
|
1010
|
+
try {
|
|
1011
|
+
strictEqual(await store.get(key("missing")), void 0);
|
|
1012
|
+
for (const [index, value] of [
|
|
1013
|
+
"text",
|
|
1014
|
+
42,
|
|
1015
|
+
true,
|
|
1016
|
+
null,
|
|
1017
|
+
[
|
|
1018
|
+
1,
|
|
1019
|
+
"two",
|
|
1020
|
+
false
|
|
1021
|
+
],
|
|
1022
|
+
{ nested: { value: "test" } }
|
|
1023
|
+
].entries()) {
|
|
1024
|
+
const valueKey = key("values", index.toString());
|
|
1025
|
+
await store.set(valueKey, value);
|
|
1026
|
+
deepStrictEqual(await store.get(valueKey), value);
|
|
1027
|
+
}
|
|
1028
|
+
const overwriteKey = key("overwrite");
|
|
1029
|
+
await store.set(overwriteKey, "before");
|
|
1030
|
+
await store.set(overwriteKey, "after");
|
|
1031
|
+
strictEqual(await store.get(overwriteKey), "after");
|
|
1032
|
+
const deleteKey = key("delete");
|
|
1033
|
+
await store.set(deleteKey, "value");
|
|
1034
|
+
await store.delete(deleteKey);
|
|
1035
|
+
strictEqual(await store.get(deleteKey), void 0);
|
|
1036
|
+
await store.delete(deleteKey);
|
|
1037
|
+
const isolatedKeys = [
|
|
1038
|
+
key("a", "b"),
|
|
1039
|
+
key("a,b"),
|
|
1040
|
+
key("a:b")
|
|
1041
|
+
];
|
|
1042
|
+
for (const [index, isolatedKey] of isolatedKeys.entries()) await store.set(isolatedKey, index);
|
|
1043
|
+
for (const [index, isolatedKey] of isolatedKeys.entries()) strictEqual(await store.get(isolatedKey), index);
|
|
1044
|
+
if (options.testTtl ?? true) {
|
|
1045
|
+
const expiringKey = key("ttl", "expiring");
|
|
1046
|
+
await store.set(expiringKey, "expired", { ttl: Temporal.Duration.from({ milliseconds: 50 }) });
|
|
1047
|
+
await delay(100);
|
|
1048
|
+
strictEqual(await store.get(expiringKey), void 0);
|
|
1049
|
+
deepStrictEqual(await collect(store.list(key("ttl"))), []);
|
|
1050
|
+
const refreshedKey = key("ttl", "refreshed");
|
|
1051
|
+
await store.set(refreshedKey, "temporary", { ttl: Temporal.Duration.from({ milliseconds: 50 }) });
|
|
1052
|
+
await store.set(refreshedKey, "persistent");
|
|
1053
|
+
await delay(100);
|
|
1054
|
+
strictEqual(await store.get(refreshedKey), "persistent");
|
|
1055
|
+
}
|
|
1056
|
+
const listPrefix = key("list");
|
|
1057
|
+
const listEntries = [
|
|
1058
|
+
{
|
|
1059
|
+
key: listPrefix,
|
|
1060
|
+
value: "exact"
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
key: key("list", "child"),
|
|
1064
|
+
value: "child"
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
key: key("list", "child", "nested"),
|
|
1068
|
+
value: "nested"
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
key: key("list-other"),
|
|
1072
|
+
value: "other"
|
|
1073
|
+
}
|
|
1074
|
+
];
|
|
1075
|
+
for (const entry of listEntries) await store.set(entry.key, entry.value);
|
|
1076
|
+
deepStrictEqual(normalize(await collect(store.list(listPrefix))), normalize(listEntries.slice(0, 3)));
|
|
1077
|
+
const prefixedEntries = await collect(store.list(prefix));
|
|
1078
|
+
deepStrictEqual(normalize(filterByPrefix(await collect(store.list()), prefix)), normalize(prefixedEntries));
|
|
1079
|
+
deepStrictEqual(normalize(filterByPrefix(await collect(store.list([])), prefix)), normalize(prefixedEntries));
|
|
1080
|
+
if (options.testCas ?? store.cas != null) {
|
|
1081
|
+
if (store.cas == null) throw new TypeError("KvStore.cas is not implemented");
|
|
1082
|
+
const cas = store.cas.bind(store);
|
|
1083
|
+
const casKey = key("cas", "value");
|
|
1084
|
+
strictEqual(await cas(casKey, "wrong", "value"), false);
|
|
1085
|
+
strictEqual(await cas(casKey, void 0, "created"), true);
|
|
1086
|
+
strictEqual(await cas(casKey, "wrong", "updated"), false);
|
|
1087
|
+
strictEqual(await cas(casKey, "created", "updated"), true);
|
|
1088
|
+
strictEqual(await store.get(casKey), "updated");
|
|
1089
|
+
strictEqual(await cas(casKey, "updated", void 0), true);
|
|
1090
|
+
strictEqual(await store.get(casKey), void 0);
|
|
1091
|
+
if (options.testTtl ?? true) {
|
|
1092
|
+
const expiredCasKey = key("cas", "expired");
|
|
1093
|
+
await store.set(expiredCasKey, "old", { ttl: Temporal.Duration.from({ milliseconds: 50 }) });
|
|
1094
|
+
await delay(100);
|
|
1095
|
+
strictEqual(await cas(expiredCasKey, void 0, "replaced"), true);
|
|
1096
|
+
const ttlCasKey = key("cas", "ttl");
|
|
1097
|
+
strictEqual(await cas(ttlCasKey, void 0, "temporary", { ttl: Temporal.Duration.from({ milliseconds: 50 }) }), true);
|
|
1098
|
+
await delay(100);
|
|
1099
|
+
strictEqual(await store.get(ttlCasKey), void 0);
|
|
1100
|
+
}
|
|
1101
|
+
const concurrentKey = key("cas", "concurrent");
|
|
1102
|
+
strictEqual((await Promise.all(Array.from({ length: 8 }, (_, index) => cas(concurrentKey, void 0, index)))).filter(Boolean).length, 1);
|
|
1103
|
+
}
|
|
1104
|
+
} finally {
|
|
1105
|
+
for await (const entry of store.list(prefix)) await store.delete(entry.key);
|
|
1106
|
+
await onFinally({ store });
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
async function collect(entries) {
|
|
1110
|
+
const results = [];
|
|
1111
|
+
for await (const entry of entries) results.push(entry);
|
|
1112
|
+
return results;
|
|
1113
|
+
}
|
|
1114
|
+
function normalize(entries) {
|
|
1115
|
+
return entries.map((entry) => JSON.stringify(entry)).sort();
|
|
1116
|
+
}
|
|
1117
|
+
function filterByPrefix(entries, prefix) {
|
|
1118
|
+
return entries.filter((entry) => prefix.every((segment, index) => entry.key[index] === segment));
|
|
1119
|
+
}
|
|
1120
|
+
//#endregion
|
|
980
1121
|
//#region src/mq-tester.ts
|
|
981
1122
|
/**
|
|
982
1123
|
* Tests a {@link MessageQueue} implementation with a standard set of tests.
|
|
@@ -1157,4 +1298,4 @@ async function waitFor(predicate, timeoutMs) {
|
|
|
1157
1298
|
}
|
|
1158
1299
|
const getRandomKey = (prefix) => `fedify_test_${prefix}_${crypto.randomUUID()}`;
|
|
1159
1300
|
//#endregion
|
|
1160
|
-
export { createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
|
|
1301
|
+
export { createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testKvStore, testMessageQueue, waitFor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/testing",
|
|
3
|
-
"version": "2.4.0-
|
|
3
|
+
"version": "2.4.0-pr.1020.43+41cebe9c",
|
|
4
4
|
"description": "Testing utilities for Fedify applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fedify",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"package.json"
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@fedify/fedify": "^2.4.0-
|
|
50
|
+
"@fedify/fedify": "^2.4.0-pr.1020.43+41cebe9c"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"es-toolkit": "1.46.1",
|
|
54
54
|
"temporal-polyfill": "^1.0.1",
|
|
55
|
-
"@fedify/vocab": "2.4.0-
|
|
56
|
-
"@fedify/vocab
|
|
55
|
+
"@fedify/vocab-runtime": "^2.4.0-pr.1020.43+41cebe9c",
|
|
56
|
+
"@fedify/vocab": "2.4.0-pr.1020.43+41cebe9c"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@standard-schema/spec": "^1.1.0",
|