@streetjs/storage 1.0.1 → 1.0.2
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/dist/tests/contract.d.ts +150 -0
- package/dist/tests/contract.d.ts.map +1 -0
- package/dist/tests/contract.js +250 -0
- package/dist/tests/contract.js.map +1 -0
- package/package.json +1 -3
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — the shared, parameterized contract-conformance suite.
|
|
3
|
+
*
|
|
4
|
+
* Requirement 2 states that every provider implements one `StorageDriver`
|
|
5
|
+
* contract with identical observable behavior: object existence, content bytes,
|
|
6
|
+
* and returned metadata must be equivalent across drivers (Requirement 2.1,
|
|
7
|
+
* 2.3), and a missing key must be reported the same way everywhere
|
|
8
|
+
* (Requirement 2.4). This module encodes the observable core of that contract
|
|
9
|
+
* as a **reusable, driver-agnostic conformance suite** so the exact same checks
|
|
10
|
+
* can be run against any {@link StorageDriver} — the zero-dependency Memory and
|
|
11
|
+
* Local drivers, the in-process testing doubles, and every cloud driver.
|
|
12
|
+
*
|
|
13
|
+
* The suite is designed to be consumed in two ways from one definition:
|
|
14
|
+
*
|
|
15
|
+
* - **Programmatically** (returns pass/fail results). {@link runStorageDriverContract}
|
|
16
|
+
* runs every check against a freshly constructed driver and returns a
|
|
17
|
+
* {@link ContractReport} describing which checks passed. This is what the
|
|
18
|
+
* `storage:verify` CLI command (task 24.2) uses to report whether the
|
|
19
|
+
* configured driver satisfies the contract, and what Property 19 (task 27.3)
|
|
20
|
+
* uses to assert that every driver conforms.
|
|
21
|
+
* - **As node:test tests**. {@link registerStorageDriverContractTests} turns each
|
|
22
|
+
* check into a `node:test` case using a caller-supplied `test` function, so a
|
|
23
|
+
* thin `*.test.js` can run the suite against Memory + Local without this
|
|
24
|
+
* support module importing `node:test` itself (keeping it importable from the
|
|
25
|
+
* CLI, which has no test runner in scope).
|
|
26
|
+
*
|
|
27
|
+
* Each check receives its **own freshly constructed driver** (from the supplied
|
|
28
|
+
* factory) so checks are fully isolated and order-independent. Assertions use
|
|
29
|
+
* `node:assert/strict`; a check throws on failure and returns normally on
|
|
30
|
+
* success. The checked behaviors are:
|
|
31
|
+
*
|
|
32
|
+
* 1. **existence** — `put` then `exists(key)` is `true`; `exists(other)` is `false`.
|
|
33
|
+
* 2. **byte round-trip** — `put(key, bytes)` then `get(key)` yields
|
|
34
|
+
* `{ found: true }` with content bytes exactly equal to the input.
|
|
35
|
+
* 3. **not-found (get)** — `get(missing)` yields `{ found: false }`.
|
|
36
|
+
* 4. **not-found (stat)** — `stat(missing)` yields `null`.
|
|
37
|
+
* 5. **metadata shape** — `put` returns the complete typed
|
|
38
|
+
* {@link StorageObjectMetadata} field set with correctly typed values.
|
|
39
|
+
*
|
|
40
|
+
* This module is a support module under `src/tests/` (not itself a `*.test.js`
|
|
41
|
+
* picked up by the default test glob); it is imported by tests and by the CLI.
|
|
42
|
+
*
|
|
43
|
+
* _Requirements: 2.1, 2.3, 2.4_
|
|
44
|
+
*/
|
|
45
|
+
import type { StorageDriver } from "../driver.js";
|
|
46
|
+
/**
|
|
47
|
+
* Report whether an optional provider SDK module specifier can be resolved in
|
|
48
|
+
* this process, without importing/evaluating it as a side effect.
|
|
49
|
+
*
|
|
50
|
+
* Several driver unit test suites (Supabase, GCS, Azure) assert that
|
|
51
|
+
* `connectXDriver` throws `StorageConfigError` specifically *because* the
|
|
52
|
+
* optional peer SDK is absent. That assertion's precondition — the SDK is
|
|
53
|
+
* unresolvable — holds by default, but Node module resolution is process-wide,
|
|
54
|
+
* not test-file-scoped: a live-integration test run that installs the SDKs (to
|
|
55
|
+
* exercise genuine round-trips; see non-s3-integration.test.ts) makes the SDK
|
|
56
|
+
* resolvable for every test file sharing that `node --test` process. Callers use
|
|
57
|
+
* this probe to honestly skip that specific guard test in that situation, rather
|
|
58
|
+
* than reporting a false failure, matching this package's established
|
|
59
|
+
* honest-skip convention (Requirement 27.3/27.4).
|
|
60
|
+
*
|
|
61
|
+
* @param {string} specifier The module specifier to probe (e.g. `"@supabase/supabase-js"`).
|
|
62
|
+
* @returns {Promise<boolean>} `true` when the specifier resolves to an installed module.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isSdkResolvable(specifier: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* A single named conformance check. {@link run} performs the check against the
|
|
67
|
+
* supplied driver and throws (via `node:assert`) when the driver violates the
|
|
68
|
+
* contract; it returns normally when the check passes.
|
|
69
|
+
*/
|
|
70
|
+
export interface ContractCheck {
|
|
71
|
+
/** Stable, human-readable check name (e.g. "byte round-trip"). */
|
|
72
|
+
readonly name: string;
|
|
73
|
+
/** The acceptance-criteria reference this check exercises. */
|
|
74
|
+
readonly requirement: string;
|
|
75
|
+
/** Run the check against `driver`; throws on violation. */
|
|
76
|
+
run(driver: StorageDriver): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/** The outcome of running a single {@link ContractCheck}. */
|
|
79
|
+
export interface ContractCheckResult {
|
|
80
|
+
readonly name: string;
|
|
81
|
+
readonly requirement: string;
|
|
82
|
+
readonly passed: boolean;
|
|
83
|
+
/** The failure message when {@link passed} is `false`. */
|
|
84
|
+
readonly error?: string;
|
|
85
|
+
}
|
|
86
|
+
/** The aggregate outcome of running the whole suite against one driver. */
|
|
87
|
+
export interface ContractReport {
|
|
88
|
+
/** The `name` of the driver under test (e.g. "memory", "local", "s3"). */
|
|
89
|
+
readonly driver: string;
|
|
90
|
+
/** `true` when every check passed. */
|
|
91
|
+
readonly passed: boolean;
|
|
92
|
+
/** Per-check results, in suite order. */
|
|
93
|
+
readonly results: readonly ContractCheckResult[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Produces a fresh {@link StorageDriver} to test. A new instance is requested
|
|
97
|
+
* for each check so the checks are isolated from one another; the factory may
|
|
98
|
+
* be async (e.g. to allocate a temp directory for the Local driver).
|
|
99
|
+
*/
|
|
100
|
+
export type StorageDriverFactory = () => StorageDriver | Promise<StorageDriver>;
|
|
101
|
+
/**
|
|
102
|
+
* A minimal `node:test`-style runner: a function taking a test name and an async
|
|
103
|
+
* body. Callers pass `test` from `node:test`; typing it structurally keeps this
|
|
104
|
+
* module free of a hard `node:test` import so it stays importable from the CLI.
|
|
105
|
+
*/
|
|
106
|
+
export type TestRunner = (name: string, fn: () => void | Promise<void>) => unknown;
|
|
107
|
+
/**
|
|
108
|
+
* The ordered set of observable-behavior checks that make up the driver
|
|
109
|
+
* contract-conformance suite. Exported so consumers can inspect, subset, or
|
|
110
|
+
* extend the list; most callers use {@link runStorageDriverContract} or
|
|
111
|
+
* {@link registerStorageDriverContractTests} instead.
|
|
112
|
+
*/
|
|
113
|
+
export declare const storageDriverContractChecks: readonly ContractCheck[];
|
|
114
|
+
/** Options for {@link runStorageDriverContract}. */
|
|
115
|
+
export interface RunContractOptions {
|
|
116
|
+
/** Override the checks to run. Defaults to {@link storageDriverContractChecks}. */
|
|
117
|
+
readonly checks?: readonly ContractCheck[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Run the contract-conformance suite against a driver and return a pass/fail
|
|
121
|
+
* {@link ContractReport}. Each check is executed against a freshly constructed
|
|
122
|
+
* driver from `makeDriver` so the checks never interfere with one another.
|
|
123
|
+
*
|
|
124
|
+
* This never throws for a contract violation — a failing check is captured as a
|
|
125
|
+
* `passed: false` entry with its error message — so a consumer such as the
|
|
126
|
+
* `storage:verify` CLI (task 24.2) can render every result. Property 19
|
|
127
|
+
* (task 27.3) uses the returned {@link ContractReport.passed} to assert
|
|
128
|
+
* conformance across drivers.
|
|
129
|
+
*
|
|
130
|
+
* @param makeDriver Factory producing a fresh driver for each check.
|
|
131
|
+
* @param options Optional override of the checks to run.
|
|
132
|
+
*/
|
|
133
|
+
export declare function runStorageDriverContract(makeDriver: StorageDriverFactory, options?: RunContractOptions): Promise<ContractReport>;
|
|
134
|
+
/**
|
|
135
|
+
* Register each contract check as an individual `node:test` case using the
|
|
136
|
+
* supplied `test` runner. A thin `*.test.js` passes `test` from `node:test` and
|
|
137
|
+
* a driver factory; each check runs against a fresh driver and fails the test if
|
|
138
|
+
* the driver violates the contract.
|
|
139
|
+
*
|
|
140
|
+
* Taking the runner as a parameter keeps this module free of a hard `node:test`
|
|
141
|
+
* import, so it remains importable from non-test contexts (e.g. the CLI's
|
|
142
|
+
* `storage:verify`).
|
|
143
|
+
*
|
|
144
|
+
* @param label Prefix for each generated test name (e.g. the driver label).
|
|
145
|
+
* @param makeDriver Factory producing a fresh driver for each check.
|
|
146
|
+
* @param runner The `test` function from `node:test` (or a compatible runner).
|
|
147
|
+
* @param checks Optional override of the checks to run.
|
|
148
|
+
*/
|
|
149
|
+
export declare function registerStorageDriverContractTests(label: string, makeDriver: StorageDriverFactory, runner: TestRunner, checks?: readonly ContractCheck[]): void;
|
|
150
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/tests/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAMlD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAO1D;AAID;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAEhF;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC;AAmEnF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,aAAa,EAuE/D,CAAC;AAIF,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,cAAc,CAAC,CA0BzB;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,oBAAoB,EAChC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,SAAS,aAAa,EAAgC,GAC7D,IAAI,CAON"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — the shared, parameterized contract-conformance suite.
|
|
3
|
+
*
|
|
4
|
+
* Requirement 2 states that every provider implements one `StorageDriver`
|
|
5
|
+
* contract with identical observable behavior: object existence, content bytes,
|
|
6
|
+
* and returned metadata must be equivalent across drivers (Requirement 2.1,
|
|
7
|
+
* 2.3), and a missing key must be reported the same way everywhere
|
|
8
|
+
* (Requirement 2.4). This module encodes the observable core of that contract
|
|
9
|
+
* as a **reusable, driver-agnostic conformance suite** so the exact same checks
|
|
10
|
+
* can be run against any {@link StorageDriver} — the zero-dependency Memory and
|
|
11
|
+
* Local drivers, the in-process testing doubles, and every cloud driver.
|
|
12
|
+
*
|
|
13
|
+
* The suite is designed to be consumed in two ways from one definition:
|
|
14
|
+
*
|
|
15
|
+
* - **Programmatically** (returns pass/fail results). {@link runStorageDriverContract}
|
|
16
|
+
* runs every check against a freshly constructed driver and returns a
|
|
17
|
+
* {@link ContractReport} describing which checks passed. This is what the
|
|
18
|
+
* `storage:verify` CLI command (task 24.2) uses to report whether the
|
|
19
|
+
* configured driver satisfies the contract, and what Property 19 (task 27.3)
|
|
20
|
+
* uses to assert that every driver conforms.
|
|
21
|
+
* - **As node:test tests**. {@link registerStorageDriverContractTests} turns each
|
|
22
|
+
* check into a `node:test` case using a caller-supplied `test` function, so a
|
|
23
|
+
* thin `*.test.js` can run the suite against Memory + Local without this
|
|
24
|
+
* support module importing `node:test` itself (keeping it importable from the
|
|
25
|
+
* CLI, which has no test runner in scope).
|
|
26
|
+
*
|
|
27
|
+
* Each check receives its **own freshly constructed driver** (from the supplied
|
|
28
|
+
* factory) so checks are fully isolated and order-independent. Assertions use
|
|
29
|
+
* `node:assert/strict`; a check throws on failure and returns normally on
|
|
30
|
+
* success. The checked behaviors are:
|
|
31
|
+
*
|
|
32
|
+
* 1. **existence** — `put` then `exists(key)` is `true`; `exists(other)` is `false`.
|
|
33
|
+
* 2. **byte round-trip** — `put(key, bytes)` then `get(key)` yields
|
|
34
|
+
* `{ found: true }` with content bytes exactly equal to the input.
|
|
35
|
+
* 3. **not-found (get)** — `get(missing)` yields `{ found: false }`.
|
|
36
|
+
* 4. **not-found (stat)** — `stat(missing)` yields `null`.
|
|
37
|
+
* 5. **metadata shape** — `put` returns the complete typed
|
|
38
|
+
* {@link StorageObjectMetadata} field set with correctly typed values.
|
|
39
|
+
*
|
|
40
|
+
* This module is a support module under `src/tests/` (not itself a `*.test.js`
|
|
41
|
+
* picked up by the default test glob); it is imported by tests and by the CLI.
|
|
42
|
+
*
|
|
43
|
+
* _Requirements: 2.1, 2.3, 2.4_
|
|
44
|
+
*/
|
|
45
|
+
import assert from "node:assert/strict";
|
|
46
|
+
import { STORAGE_METADATA_FIELDS } from "../metadata.js";
|
|
47
|
+
// ── Optional-SDK resolvability probe ──────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Report whether an optional provider SDK module specifier can be resolved in
|
|
50
|
+
* this process, without importing/evaluating it as a side effect.
|
|
51
|
+
*
|
|
52
|
+
* Several driver unit test suites (Supabase, GCS, Azure) assert that
|
|
53
|
+
* `connectXDriver` throws `StorageConfigError` specifically *because* the
|
|
54
|
+
* optional peer SDK is absent. That assertion's precondition — the SDK is
|
|
55
|
+
* unresolvable — holds by default, but Node module resolution is process-wide,
|
|
56
|
+
* not test-file-scoped: a live-integration test run that installs the SDKs (to
|
|
57
|
+
* exercise genuine round-trips; see non-s3-integration.test.ts) makes the SDK
|
|
58
|
+
* resolvable for every test file sharing that `node --test` process. Callers use
|
|
59
|
+
* this probe to honestly skip that specific guard test in that situation, rather
|
|
60
|
+
* than reporting a false failure, matching this package's established
|
|
61
|
+
* honest-skip convention (Requirement 27.3/27.4).
|
|
62
|
+
*
|
|
63
|
+
* @param {string} specifier The module specifier to probe (e.g. `"@supabase/supabase-js"`).
|
|
64
|
+
* @returns {Promise<boolean>} `true` when the specifier resolves to an installed module.
|
|
65
|
+
*/
|
|
66
|
+
export function isSdkResolvable(specifier) {
|
|
67
|
+
try {
|
|
68
|
+
import.meta.resolve(specifier);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ── Test fixtures ─────────────────────────────────────────────────────────────
|
|
76
|
+
/** Encode a UTF-8 string into a Uint8Array for storage. */
|
|
77
|
+
function bytesOf(str) {
|
|
78
|
+
return new TextEncoder().encode(str);
|
|
79
|
+
}
|
|
80
|
+
/** The full set of access levels, used to validate the `accessLevel` field. */
|
|
81
|
+
const ACCESS_LEVELS = [
|
|
82
|
+
"public",
|
|
83
|
+
"private",
|
|
84
|
+
"signed",
|
|
85
|
+
"authenticated",
|
|
86
|
+
"role-based",
|
|
87
|
+
"tenant-aware",
|
|
88
|
+
];
|
|
89
|
+
/**
|
|
90
|
+
* Assert that `metadata` carries the complete typed {@link StorageObjectMetadata}
|
|
91
|
+
* field set with correctly typed values. Uses {@link STORAGE_METADATA_FIELDS} as
|
|
92
|
+
* the single source of truth for the required field names so this check tracks
|
|
93
|
+
* the canonical shape automatically (Requirement 2.1, 10.1).
|
|
94
|
+
*/
|
|
95
|
+
function assertMetadataShape(metadata, expected) {
|
|
96
|
+
assert.ok(metadata !== null && typeof metadata === "object", "put must return a metadata object");
|
|
97
|
+
// Every canonical field must be present as an own key.
|
|
98
|
+
for (const field of STORAGE_METADATA_FIELDS) {
|
|
99
|
+
assert.ok(field in metadata, `metadata is missing required field "${field}"`);
|
|
100
|
+
}
|
|
101
|
+
// Identity / value fields must be correctly typed.
|
|
102
|
+
assert.equal(metadata.key, expected.key, "metadata.key must equal the stored key");
|
|
103
|
+
assert.equal(metadata.size, expected.size, "metadata.size must equal the byte length");
|
|
104
|
+
assert.equal(typeof metadata.contentType, "string", "metadata.contentType must be a string");
|
|
105
|
+
assert.equal(typeof metadata.etag, "string", "metadata.etag must be a string");
|
|
106
|
+
assert.equal(typeof metadata.checksum, "string", "metadata.checksum must be a string");
|
|
107
|
+
assert.equal(typeof metadata.createdAt, "number", "metadata.createdAt must be a number");
|
|
108
|
+
assert.equal(typeof metadata.updatedAt, "number", "metadata.updatedAt must be a number");
|
|
109
|
+
assert.ok(metadata.custom !== null && typeof metadata.custom === "object", "metadata.custom must be an object");
|
|
110
|
+
assert.ok(ACCESS_LEVELS.includes(metadata.accessLevel), `metadata.accessLevel must be a valid AccessLevel (got "${metadata.accessLevel}")`);
|
|
111
|
+
// owner/tenant are optional: absent (undefined) or a string.
|
|
112
|
+
if (metadata.owner !== undefined) {
|
|
113
|
+
assert.equal(typeof metadata.owner, "string", "metadata.owner must be a string when present");
|
|
114
|
+
}
|
|
115
|
+
if (metadata.tenant !== undefined) {
|
|
116
|
+
assert.equal(typeof metadata.tenant, "string", "metadata.tenant must be a string when present");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ── The checks ────────────────────────────────────────────────────────────────
|
|
120
|
+
/**
|
|
121
|
+
* The ordered set of observable-behavior checks that make up the driver
|
|
122
|
+
* contract-conformance suite. Exported so consumers can inspect, subset, or
|
|
123
|
+
* extend the list; most callers use {@link runStorageDriverContract} or
|
|
124
|
+
* {@link registerStorageDriverContractTests} instead.
|
|
125
|
+
*/
|
|
126
|
+
export const storageDriverContractChecks = [
|
|
127
|
+
{
|
|
128
|
+
name: "existence: put then exists reports presence and absence",
|
|
129
|
+
requirement: "2.1",
|
|
130
|
+
async run(driver) {
|
|
131
|
+
const key = "contract/existence.txt";
|
|
132
|
+
assert.equal(await driver.exists(key), false, "exists must be false before the object is stored");
|
|
133
|
+
await driver.put(key, bytesOf("present"), {});
|
|
134
|
+
assert.equal(await driver.exists(key), true, "exists must be true after put");
|
|
135
|
+
assert.equal(await driver.exists("contract/never-written.txt"), false, "exists must be false for an unrelated key");
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "byte round-trip: get returns the stored bytes unchanged",
|
|
140
|
+
requirement: "2.1",
|
|
141
|
+
async run(driver) {
|
|
142
|
+
const key = "contract/round-trip.bin";
|
|
143
|
+
// Include boundary byte values so a lossy round-trip is caught.
|
|
144
|
+
const content = new Uint8Array([0, 1, 127, 128, 254, 255, 65, 66, 67]);
|
|
145
|
+
await driver.put(key, content, {});
|
|
146
|
+
const result = await driver.get(key);
|
|
147
|
+
assert.equal(result.found, true, "get must report found:true for a stored key");
|
|
148
|
+
assert.ok(result.found === true, "narrowing: result must be the found variant");
|
|
149
|
+
assert.deepEqual(result.bytes, content, "get must return the exact stored bytes");
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "not-found (get): missing key reports found:false",
|
|
154
|
+
requirement: "2.4",
|
|
155
|
+
async run(driver) {
|
|
156
|
+
const result = await driver.get("contract/absent-get.txt");
|
|
157
|
+
assert.equal(result.found, false, "get on a missing key must report found:false");
|
|
158
|
+
assert.ok(result.found === false, "the not-found result must be the { found: false } variant");
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "not-found (stat): missing key reports null",
|
|
163
|
+
requirement: "2.4",
|
|
164
|
+
async run(driver) {
|
|
165
|
+
const meta = await driver.stat("contract/absent-stat.txt");
|
|
166
|
+
assert.equal(meta, null, "stat on a missing key must return null");
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "metadata shape: put returns the complete typed field set",
|
|
171
|
+
requirement: "2.1",
|
|
172
|
+
async run(driver) {
|
|
173
|
+
const key = "contract/metadata-shape.txt";
|
|
174
|
+
const content = bytesOf("shape check");
|
|
175
|
+
const metadata = await driver.put(key, content, {
|
|
176
|
+
contentType: "text/plain",
|
|
177
|
+
owner: "user-1",
|
|
178
|
+
tenant: "tenant-a",
|
|
179
|
+
accessLevel: "public",
|
|
180
|
+
custom: { label: "invoice" },
|
|
181
|
+
});
|
|
182
|
+
assertMetadataShape(metadata, { key, size: content.byteLength });
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* Run the contract-conformance suite against a driver and return a pass/fail
|
|
188
|
+
* {@link ContractReport}. Each check is executed against a freshly constructed
|
|
189
|
+
* driver from `makeDriver` so the checks never interfere with one another.
|
|
190
|
+
*
|
|
191
|
+
* This never throws for a contract violation — a failing check is captured as a
|
|
192
|
+
* `passed: false` entry with its error message — so a consumer such as the
|
|
193
|
+
* `storage:verify` CLI (task 24.2) can render every result. Property 19
|
|
194
|
+
* (task 27.3) uses the returned {@link ContractReport.passed} to assert
|
|
195
|
+
* conformance across drivers.
|
|
196
|
+
*
|
|
197
|
+
* @param makeDriver Factory producing a fresh driver for each check.
|
|
198
|
+
* @param options Optional override of the checks to run.
|
|
199
|
+
*/
|
|
200
|
+
export async function runStorageDriverContract(makeDriver, options) {
|
|
201
|
+
const checks = options?.checks ?? storageDriverContractChecks;
|
|
202
|
+
const results = [];
|
|
203
|
+
let driverName = "unknown";
|
|
204
|
+
for (const check of checks) {
|
|
205
|
+
const driver = await makeDriver();
|
|
206
|
+
driverName = driver.name;
|
|
207
|
+
try {
|
|
208
|
+
await check.run(driver);
|
|
209
|
+
results.push({ name: check.name, requirement: check.requirement, passed: true });
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
results.push({
|
|
213
|
+
name: check.name,
|
|
214
|
+
requirement: check.requirement,
|
|
215
|
+
passed: false,
|
|
216
|
+
error: error instanceof Error ? error.message : String(error),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
driver: driverName,
|
|
222
|
+
passed: results.every((result) => result.passed),
|
|
223
|
+
results,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ── node:test integration ─────────────────────────────────────────────────────
|
|
227
|
+
/**
|
|
228
|
+
* Register each contract check as an individual `node:test` case using the
|
|
229
|
+
* supplied `test` runner. A thin `*.test.js` passes `test` from `node:test` and
|
|
230
|
+
* a driver factory; each check runs against a fresh driver and fails the test if
|
|
231
|
+
* the driver violates the contract.
|
|
232
|
+
*
|
|
233
|
+
* Taking the runner as a parameter keeps this module free of a hard `node:test`
|
|
234
|
+
* import, so it remains importable from non-test contexts (e.g. the CLI's
|
|
235
|
+
* `storage:verify`).
|
|
236
|
+
*
|
|
237
|
+
* @param label Prefix for each generated test name (e.g. the driver label).
|
|
238
|
+
* @param makeDriver Factory producing a fresh driver for each check.
|
|
239
|
+
* @param runner The `test` function from `node:test` (or a compatible runner).
|
|
240
|
+
* @param checks Optional override of the checks to run.
|
|
241
|
+
*/
|
|
242
|
+
export function registerStorageDriverContractTests(label, makeDriver, runner, checks = storageDriverContractChecks) {
|
|
243
|
+
for (const check of checks) {
|
|
244
|
+
runner(`${label}: ${check.name}`, async () => {
|
|
245
|
+
const driver = await makeDriver();
|
|
246
|
+
await check.run(driver);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/tests/contract.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streetjs/storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "StreetJS unified storage framework: a strongly-typed, plugin-first, driver-based object storage layer with zero-dependency in-memory and filesystem drivers, streaming, validation, metadata, multipart, resumable uploads, signed URLs, versioning, lifecycle, access control, and optional cloud provider integrations (S3, R2, Supabase, GCS, Azure, MinIO, Backblaze) isolated behind optional submodules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,8 +52,6 @@
|
|
|
52
52
|
"!dist/**/*.test.js.map",
|
|
53
53
|
"!dist/**/*.test.d.ts",
|
|
54
54
|
"!dist/**/*.test.d.ts.map",
|
|
55
|
-
"!dist/tests/**",
|
|
56
|
-
"!dist/**/__tests__/**",
|
|
57
55
|
"README.md",
|
|
58
56
|
"CHANGELOG.md",
|
|
59
57
|
"LICENSE"
|