@hot-updater/cloudflare 1.0.0-rc.2 → 1.0.0-rc.21
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/index.cjs +95 -870
- package/dist/index.d.cts +724 -6
- package/dist/index.d.mts +724 -6
- package/dist/index.mjs +93 -870
- package/dist/init/index.cjs +11768 -0
- package/dist/init/index.d.cts +5 -1
- package/dist/init/index.d.mts +5 -1
- package/dist/init/index.mjs +11761 -1
- package/dist/worker/index.cjs +59 -859
- package/dist/worker/index.d.cts +725 -11
- package/dist/worker/index.d.mts +725 -11
- package/dist/worker/index.mjs +62 -863
- package/package.json +9 -13
- package/sql/bundles.sql +109 -190
- package/src/d1Database.ts +61 -40
- package/src/d1Executor.ts +84 -0
- package/src/d1Schema.ts +26 -0
- package/src/index.ts +1 -0
- package/src/plugins.ts +8 -0
- package/src/r2S3Storage.ts +4 -4
- package/src/r2Storage.ts +2 -2
- package/src/r2WorkerStorage.ts +4 -4
- package/src/worker/d1Database.ts +21 -27
- package/src/worker/index.ts +1 -0
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +9682 -6206
- package/worker/dist/index.js.map +4 -4
- package/worker/migrations/0001_hot-updater_1.0.0.sql +109 -205
- package/worker/src/index.ts +2 -2
- package/dist/iac/index.cjs +0 -12566
- package/dist/iac/index.d.cts +0 -9
- package/dist/iac/index.d.mts +0 -9
- package/dist/iac/index.mjs +0 -12558
- package/src/d1Database.spec.ts +0 -178
- package/src/d1Implementation.spec.ts +0 -252
- package/src/d1Implementation.ts +0 -742
- package/src/d1Query.spec.ts +0 -46
- package/src/d1Query.ts +0 -89
- package/src/d1Rows.spec.ts +0 -111
- package/src/d1Rows.ts +0 -326
- package/src/d1Sql.spec.ts +0 -30
- package/src/d1Sql.ts +0 -175
- package/src/r2Storage.spec.ts +0 -346
- package/src/r2WorkerStorage.spec.ts +0 -184
- package/src/utils/createWrangler.spec.ts +0 -75
- package/src/worker/d1Database.spec.ts +0 -33
- package/worker/src/databaseMigration.integration.spec.ts +0 -130
- package/worker/src/databasePlugins.integration.spec.ts +0 -292
- package/worker/src/index.integration.spec.ts +0 -190
- package/worker/src/storagePlugin.integration.spec.ts +0 -59
- package/worker/src/testWorker.ts +0 -5
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
const mocks = vi.hoisted(() => ({
|
|
4
|
-
command: vi.fn(),
|
|
5
|
-
execa: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
|
|
8
|
-
vi.mock("execa", () => ({
|
|
9
|
-
execa: mocks.execa,
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
import { createWrangler } from "./createWrangler";
|
|
13
|
-
|
|
14
|
-
describe("createWrangler", () => {
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
vi.clearAllMocks();
|
|
17
|
-
mocks.execa.mockReturnValue(mocks.command);
|
|
18
|
-
mocks.command.mockResolvedValue({});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("marks replay commands as CI so Wrangler skips nested prompts", async () => {
|
|
22
|
-
const wrangler = createWrangler({
|
|
23
|
-
accountId: "account-id",
|
|
24
|
-
cloudflareApiToken: "api-token",
|
|
25
|
-
cwd: "/tmp/cloudflare-init",
|
|
26
|
-
nonInteractive: true,
|
|
27
|
-
stdio: "inherit",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
await wrangler("d1", "migrations", "apply", "hot-updater", "--remote");
|
|
31
|
-
|
|
32
|
-
expect(mocks.execa).toHaveBeenCalledWith({
|
|
33
|
-
cwd: "/tmp/cloudflare-init",
|
|
34
|
-
env: {
|
|
35
|
-
CI: "true",
|
|
36
|
-
CLOUDFLARE_ACCOUNT_ID: "account-id",
|
|
37
|
-
CLOUDFLARE_API_TOKEN: "api-token",
|
|
38
|
-
},
|
|
39
|
-
extendsEnv: true,
|
|
40
|
-
shell: false,
|
|
41
|
-
stdio: "inherit",
|
|
42
|
-
});
|
|
43
|
-
expect(mocks.command).toHaveBeenCalledWith("npx", [
|
|
44
|
-
"wrangler",
|
|
45
|
-
"d1",
|
|
46
|
-
"migrations",
|
|
47
|
-
"apply",
|
|
48
|
-
"hot-updater",
|
|
49
|
-
"--remote",
|
|
50
|
-
]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("passes shell metacharacters as one argument", async () => {
|
|
54
|
-
const wrangler = createWrangler({
|
|
55
|
-
accountId: "account-id",
|
|
56
|
-
cloudflareApiToken: "api-token",
|
|
57
|
-
cwd: "/tmp/cloudflare-init",
|
|
58
|
-
stdio: "inherit",
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
await wrangler("deploy", "--name", "safe; printf INJECTED");
|
|
62
|
-
|
|
63
|
-
expect(mocks.execa).toHaveBeenCalledWith(
|
|
64
|
-
expect.objectContaining({
|
|
65
|
-
shell: false,
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
expect(mocks.command).toHaveBeenCalledWith("npx", [
|
|
69
|
-
"wrangler",
|
|
70
|
-
"deploy",
|
|
71
|
-
"--name",
|
|
72
|
-
"safe; printf INJECTED",
|
|
73
|
-
]);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { d1Database, type D1Like } from "./d1Database";
|
|
4
|
-
|
|
5
|
-
const database: D1Like = {
|
|
6
|
-
prepare: () => ({
|
|
7
|
-
bind: () => ({
|
|
8
|
-
all: async () => ({
|
|
9
|
-
results: [{ id: "channel-production", name: "production" }],
|
|
10
|
-
}),
|
|
11
|
-
}),
|
|
12
|
-
}),
|
|
13
|
-
batch: async (statements) =>
|
|
14
|
-
Promise.all(statements.map((item) => item.all())),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
it("uses the D1 binding supplied at the Worker composition boundary", async () => {
|
|
18
|
-
const plugin = d1Database(database);
|
|
19
|
-
|
|
20
|
-
expect(plugin.name).toBe("d1Database");
|
|
21
|
-
expect(Object.keys(plugin.models).sort()).toEqual([
|
|
22
|
-
"apiKeys",
|
|
23
|
-
"bundlePatches",
|
|
24
|
-
"bundles",
|
|
25
|
-
"channels",
|
|
26
|
-
"insights",
|
|
27
|
-
"releaseCatalogs",
|
|
28
|
-
"releases",
|
|
29
|
-
]);
|
|
30
|
-
await expect(plugin.models.channels.list({})).resolves.toEqual({
|
|
31
|
-
channels: [{ id: "channel-production", name: "production" }],
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { env } from "cloudflare:test";
|
|
2
|
-
import { expect, inject, it } from "vitest";
|
|
3
|
-
|
|
4
|
-
declare module "vitest" {
|
|
5
|
-
export interface ProvidedContext {
|
|
6
|
-
d1Migrations: readonly {
|
|
7
|
-
readonly name: string;
|
|
8
|
-
readonly sql: string;
|
|
9
|
-
}[];
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
it("ships a single D1 1.0.0 CREATE migration", () => {
|
|
14
|
-
expect(inject("d1Migrations").map(({ name }) => name)).toEqual([
|
|
15
|
-
"0001_hot-updater_1.0.0.sql",
|
|
16
|
-
]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("creates the current schema with required artifact sizes", async () => {
|
|
20
|
-
const [createMigration] = inject("d1Migrations");
|
|
21
|
-
await env.DB.prepare(createMigration!.sql).run();
|
|
22
|
-
await env.DB.prepare(`
|
|
23
|
-
INSERT INTO bundles (
|
|
24
|
-
id, platform, file_hash, storage_uri, archive_byte_size, metadata
|
|
25
|
-
) VALUES (
|
|
26
|
-
'00000000-0000-0000-0000-000000000001', 'ios', 'hash',
|
|
27
|
-
'storage://bundle', 3000000001, '{}'
|
|
28
|
-
)
|
|
29
|
-
`).run();
|
|
30
|
-
await env.DB.prepare(`
|
|
31
|
-
INSERT INTO bundle_patches (
|
|
32
|
-
id, bundle_id, base_bundle_id, base_file_hash, patch_file_hash,
|
|
33
|
-
patch_storage_uri, byte_size
|
|
34
|
-
) VALUES (
|
|
35
|
-
'patch-1', '00000000-0000-0000-0000-000000000001',
|
|
36
|
-
'00000000-0000-0000-0000-000000000001', 'base-hash', 'patch-hash',
|
|
37
|
-
'storage://patch', 3000000002
|
|
38
|
-
)
|
|
39
|
-
`).run();
|
|
40
|
-
|
|
41
|
-
const tables = await env.DB.prepare(`
|
|
42
|
-
SELECT name FROM sqlite_master
|
|
43
|
-
WHERE type = 'table'
|
|
44
|
-
ORDER BY name
|
|
45
|
-
`).all<{ name: string }>();
|
|
46
|
-
|
|
47
|
-
expect(tables.results.map(({ name }) => name)).toEqual(
|
|
48
|
-
expect.arrayContaining([
|
|
49
|
-
"bundle_events",
|
|
50
|
-
"bundle_patches",
|
|
51
|
-
"bundles",
|
|
52
|
-
"channels",
|
|
53
|
-
"api_keys",
|
|
54
|
-
"private_hot_updater_settings",
|
|
55
|
-
"release_catalogs",
|
|
56
|
-
"releases",
|
|
57
|
-
]),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const version = await env.DB.prepare(
|
|
61
|
-
"SELECT value FROM private_hot_updater_settings WHERE key = 'schema.core'",
|
|
62
|
-
).first<string>("value");
|
|
63
|
-
expect(version).toBe("1.0.0");
|
|
64
|
-
|
|
65
|
-
await env.DB.prepare(
|
|
66
|
-
"INSERT INTO channels (id, name) VALUES ('channel-1', 'production')",
|
|
67
|
-
).run();
|
|
68
|
-
await env.DB.prepare(`
|
|
69
|
-
INSERT INTO releases (
|
|
70
|
-
id, revision, scope_key, channel_id, platform, kind, bundle_id,
|
|
71
|
-
strategy, target_app_version, fingerprint_hash, enabled,
|
|
72
|
-
should_force_update, message, rollout_cohort_count, target_cohorts,
|
|
73
|
-
operation, source_release_id, created_at_ms, updated_at_ms
|
|
74
|
-
) VALUES (
|
|
75
|
-
'00000000-0000-0000-0000-000000000001', 1, 'scope', 'channel-1',
|
|
76
|
-
'ios', 'BUNDLE', '00000000-0000-0000-0000-000000000001',
|
|
77
|
-
'APP_VERSION', '1.0.0', NULL, 1, 0, NULL, 1000, '[]',
|
|
78
|
-
'DEPLOY', NULL, 0, 0
|
|
79
|
-
)
|
|
80
|
-
`).run();
|
|
81
|
-
|
|
82
|
-
const release = await env.DB.prepare(
|
|
83
|
-
"SELECT channel_id, bundle_id FROM releases",
|
|
84
|
-
).first();
|
|
85
|
-
expect(release).toEqual({
|
|
86
|
-
channel_id: "channel-1",
|
|
87
|
-
bundle_id: "00000000-0000-0000-0000-000000000001",
|
|
88
|
-
});
|
|
89
|
-
const sizes = await env.DB.prepare(`
|
|
90
|
-
SELECT bundle.archive_byte_size, patch.byte_size
|
|
91
|
-
FROM bundles AS bundle
|
|
92
|
-
JOIN bundle_patches AS patch ON patch.bundle_id = bundle.id
|
|
93
|
-
`).first();
|
|
94
|
-
expect(sizes).toEqual({
|
|
95
|
-
archive_byte_size: 3_000_000_001,
|
|
96
|
-
byte_size: 3_000_000_002,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
for (const size of [-1, Number.MAX_SAFE_INTEGER + 1, null]) {
|
|
100
|
-
await expect(
|
|
101
|
-
env.DB.prepare("UPDATE bundles SET archive_byte_size = ?")
|
|
102
|
-
.bind(size)
|
|
103
|
-
.run(),
|
|
104
|
-
).rejects.toThrow(/constraint failed/);
|
|
105
|
-
await expect(
|
|
106
|
-
env.DB.prepare("UPDATE bundle_patches SET byte_size = ?")
|
|
107
|
-
.bind(size)
|
|
108
|
-
.run(),
|
|
109
|
-
).rejects.toThrow(/constraint failed/);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await expect(
|
|
113
|
-
env.DB.prepare(`
|
|
114
|
-
INSERT INTO bundles (id, platform, file_hash, storage_uri)
|
|
115
|
-
VALUES ('missing-size', 'ios', 'hash', 'storage://bundle')
|
|
116
|
-
`).run(),
|
|
117
|
-
).rejects.toThrow(/NOT NULL constraint failed/);
|
|
118
|
-
await expect(
|
|
119
|
-
env.DB.prepare(`
|
|
120
|
-
INSERT INTO bundle_patches (
|
|
121
|
-
id, bundle_id, base_bundle_id, base_file_hash, patch_file_hash,
|
|
122
|
-
patch_storage_uri
|
|
123
|
-
) VALUES (
|
|
124
|
-
'missing-size', '00000000-0000-0000-0000-000000000001',
|
|
125
|
-
'00000000-0000-0000-0000-000000000001', 'base-hash', 'patch-hash',
|
|
126
|
-
'storage://patch'
|
|
127
|
-
)
|
|
128
|
-
`).run(),
|
|
129
|
-
).rejects.toThrow(/NOT NULL constraint failed/);
|
|
130
|
-
});
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BundleRow,
|
|
3
|
-
ChannelRow,
|
|
4
|
-
ReleaseRow,
|
|
5
|
-
} from "@hot-updater/plugin-core";
|
|
6
|
-
import { setupDatabasePluginTestSuite } from "@hot-updater/test-utils";
|
|
7
|
-
import { env } from "cloudflare:test";
|
|
8
|
-
import {
|
|
9
|
-
afterAll,
|
|
10
|
-
beforeAll,
|
|
11
|
-
beforeEach,
|
|
12
|
-
describe,
|
|
13
|
-
expect,
|
|
14
|
-
inject,
|
|
15
|
-
it,
|
|
16
|
-
vi,
|
|
17
|
-
} from "vitest";
|
|
18
|
-
|
|
19
|
-
import { d1Database } from "../../src/d1Database";
|
|
20
|
-
import { d1Database as d1RuntimeDatabase } from "../../src/worker";
|
|
21
|
-
|
|
22
|
-
const state = vi.hoisted<{ db: D1Database | undefined }>(() => ({
|
|
23
|
-
db: undefined,
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
class D1TestStateError extends Error {
|
|
27
|
-
readonly name = "D1TestStateError";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const getDb = (): D1Database => {
|
|
31
|
-
if (state.db === undefined) {
|
|
32
|
-
throw new D1TestStateError();
|
|
33
|
-
}
|
|
34
|
-
return state.db;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
vi.mock("cloudflare", () => ({
|
|
38
|
-
default: class MockCloudflare {
|
|
39
|
-
readonly d1 = {
|
|
40
|
-
database: {
|
|
41
|
-
query: async (
|
|
42
|
-
_databaseId: string,
|
|
43
|
-
input:
|
|
44
|
-
| { readonly sql: string; readonly params?: readonly string[] }
|
|
45
|
-
| {
|
|
46
|
-
readonly batch: readonly {
|
|
47
|
-
readonly sql: string;
|
|
48
|
-
readonly params?: readonly string[];
|
|
49
|
-
}[];
|
|
50
|
-
},
|
|
51
|
-
) => {
|
|
52
|
-
const statements =
|
|
53
|
-
"batch" in input
|
|
54
|
-
? input.batch.map(({ sql, params }) =>
|
|
55
|
-
getDb()
|
|
56
|
-
.prepare(sql)
|
|
57
|
-
.bind(...(params ?? [])),
|
|
58
|
-
)
|
|
59
|
-
: (() => {
|
|
60
|
-
const params = input.params ?? [];
|
|
61
|
-
let paramOffset = 0;
|
|
62
|
-
return input.sql
|
|
63
|
-
.split(";")
|
|
64
|
-
.map((sql) => sql.trim())
|
|
65
|
-
.filter(Boolean)
|
|
66
|
-
.map((sql) => {
|
|
67
|
-
const paramCount = sql.match(/\?/g)?.length ?? 0;
|
|
68
|
-
const statement = getDb()
|
|
69
|
-
.prepare(sql)
|
|
70
|
-
.bind(
|
|
71
|
-
...params.slice(
|
|
72
|
-
paramOffset,
|
|
73
|
-
paramOffset + paramCount,
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
paramOffset += paramCount;
|
|
77
|
-
return statement;
|
|
78
|
-
});
|
|
79
|
-
})();
|
|
80
|
-
const results = await getDb().batch(statements);
|
|
81
|
-
return {
|
|
82
|
-
async *iterPages() {
|
|
83
|
-
yield { result: results };
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
const reset = async (): Promise<void> => {
|
|
93
|
-
await getDb()
|
|
94
|
-
.prepare(
|
|
95
|
-
"DELETE FROM bundle_events; DELETE FROM api_keys; DELETE FROM bundle_patches; DELETE FROM release_catalogs; DELETE FROM releases; DELETE FROM bundles; DELETE FROM channels;",
|
|
96
|
-
)
|
|
97
|
-
.run();
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const createChannelRow = (name: string): ChannelRow => ({
|
|
101
|
-
id: `channel-${name}`,
|
|
102
|
-
name,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const createBundleRow = (): BundleRow => ({
|
|
106
|
-
id: "00000000-0000-0000-0000-000000000902",
|
|
107
|
-
platform: "ios",
|
|
108
|
-
file_hash: "hash",
|
|
109
|
-
git_commit_hash: null,
|
|
110
|
-
storage_uri: "storage://bundle",
|
|
111
|
-
archive_byte_size: 3_000_000_001,
|
|
112
|
-
metadata: {},
|
|
113
|
-
manifest_storage_uri: null,
|
|
114
|
-
manifest_file_hash: null,
|
|
115
|
-
asset_base_storage_uri: null,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const createReleaseRow = (
|
|
119
|
-
channel: ChannelRow,
|
|
120
|
-
bundle: BundleRow,
|
|
121
|
-
): ReleaseRow => ({
|
|
122
|
-
id: "00000000-0000-7000-8000-000000000903",
|
|
123
|
-
revision: 1,
|
|
124
|
-
scope_key: `v1:test:${channel.name}:ios:app-version`,
|
|
125
|
-
channel_id: channel.id,
|
|
126
|
-
platform: bundle.platform,
|
|
127
|
-
kind: "BUNDLE",
|
|
128
|
-
bundle_id: bundle.id,
|
|
129
|
-
strategy: "APP_VERSION",
|
|
130
|
-
target_app_version: "1.0.0",
|
|
131
|
-
fingerprint_hash: null,
|
|
132
|
-
enabled: true,
|
|
133
|
-
should_force_update: false,
|
|
134
|
-
message: null,
|
|
135
|
-
rollout_cohort_count: 1000,
|
|
136
|
-
target_cohorts: [],
|
|
137
|
-
operation: "DEPLOY",
|
|
138
|
-
source_release_id: null,
|
|
139
|
-
created_at_ms: 100,
|
|
140
|
-
updated_at_ms: 100,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
setupDatabasePluginTestSuite({
|
|
144
|
-
name: "cloudflare d1 http fixed-model database plugin",
|
|
145
|
-
migrate: async () => {
|
|
146
|
-
state.db = env.DB;
|
|
147
|
-
await getDb().prepare(inject("prepareSql")).run();
|
|
148
|
-
},
|
|
149
|
-
createPlugin: () =>
|
|
150
|
-
d1Database({
|
|
151
|
-
accountId: "account-id",
|
|
152
|
-
cloudflareApiToken: "api-token",
|
|
153
|
-
databaseId: "database-id",
|
|
154
|
-
}),
|
|
155
|
-
reset,
|
|
156
|
-
dispose: () => undefined,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
setupDatabasePluginTestSuite({
|
|
160
|
-
name: "cloudflare worker d1 fixed-model database plugin",
|
|
161
|
-
migrate: () => undefined,
|
|
162
|
-
createPlugin: () => d1RuntimeDatabase(env.DB),
|
|
163
|
-
reset,
|
|
164
|
-
dispose: () => {
|
|
165
|
-
state.db = undefined;
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe.each([
|
|
170
|
-
{
|
|
171
|
-
name: "cloudflare d1 http",
|
|
172
|
-
createPlugin: () =>
|
|
173
|
-
d1Database({
|
|
174
|
-
accountId: "account-id",
|
|
175
|
-
cloudflareApiToken: "api-token",
|
|
176
|
-
databaseId: "database-id",
|
|
177
|
-
}),
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
name: "cloudflare worker d1",
|
|
181
|
-
createPlugin: () => d1RuntimeDatabase(env.DB),
|
|
182
|
-
},
|
|
183
|
-
])("$name Channel deletion", ({ createPlugin }) => {
|
|
184
|
-
beforeAll(() => {
|
|
185
|
-
state.db = env.DB;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
beforeEach(async () => {
|
|
189
|
-
await reset();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
afterAll(() => {
|
|
193
|
-
state.db = undefined;
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("deletes only empty channels", async () => {
|
|
197
|
-
const plugin = createPlugin();
|
|
198
|
-
const channel = createChannelRow("empty");
|
|
199
|
-
await plugin.models.channels.insert({
|
|
200
|
-
row: channel,
|
|
201
|
-
onConflict: "returnExisting",
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
await expect(
|
|
205
|
-
plugin.models.channels.delete({ id: channel.id }),
|
|
206
|
-
).resolves.toEqual({ deleted: true });
|
|
207
|
-
await expect(
|
|
208
|
-
plugin.models.channels.delete({ id: channel.id }),
|
|
209
|
-
).resolves.toEqual({ deleted: false, reason: "not_found" });
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("rejects direct and committed deletion while a Release references the channel", async () => {
|
|
213
|
-
const plugin = createPlugin();
|
|
214
|
-
const channel = createChannelRow("active");
|
|
215
|
-
const bundle = createBundleRow();
|
|
216
|
-
const release = createReleaseRow(channel, bundle);
|
|
217
|
-
await plugin.models.channels.insert({
|
|
218
|
-
row: channel,
|
|
219
|
-
onConflict: "returnExisting",
|
|
220
|
-
});
|
|
221
|
-
await plugin.commit({
|
|
222
|
-
changes: [
|
|
223
|
-
{ model: "bundles", operation: "insert", row: bundle },
|
|
224
|
-
{ model: "releases", operation: "insert", row: release },
|
|
225
|
-
],
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
await expect(
|
|
229
|
-
plugin.models.channels.delete({ id: channel.id }),
|
|
230
|
-
).resolves.toEqual({ deleted: false, reason: "not_empty" });
|
|
231
|
-
await expect(
|
|
232
|
-
plugin.commit({
|
|
233
|
-
changes: [
|
|
234
|
-
{
|
|
235
|
-
model: "channels",
|
|
236
|
-
operation: "delete",
|
|
237
|
-
where: { id: channel.id },
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
}),
|
|
241
|
-
).resolves.toEqual({
|
|
242
|
-
committed: false,
|
|
243
|
-
conflict: { changeIndex: 0, reason: "referenced" },
|
|
244
|
-
});
|
|
245
|
-
await expect(plugin.models.bundles.findById(bundle.id)).resolves.toEqual(
|
|
246
|
-
bundle,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("atomically deletes a Release before deleting its newly empty channel", async () => {
|
|
251
|
-
const plugin = createPlugin();
|
|
252
|
-
const channel = createChannelRow("retired");
|
|
253
|
-
const bundle = createBundleRow();
|
|
254
|
-
const release = createReleaseRow(channel, bundle);
|
|
255
|
-
await plugin.models.channels.insert({
|
|
256
|
-
row: channel,
|
|
257
|
-
onConflict: "returnExisting",
|
|
258
|
-
});
|
|
259
|
-
await plugin.commit({
|
|
260
|
-
changes: [
|
|
261
|
-
{ model: "bundles", operation: "insert", row: bundle },
|
|
262
|
-
{ model: "releases", operation: "insert", row: release },
|
|
263
|
-
],
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
await expect(
|
|
267
|
-
plugin.commit({
|
|
268
|
-
changes: [
|
|
269
|
-
{
|
|
270
|
-
model: "releases",
|
|
271
|
-
operation: "delete",
|
|
272
|
-
where: { id: release.id },
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
model: "channels",
|
|
276
|
-
operation: "delete",
|
|
277
|
-
where: { id: channel.id },
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
}),
|
|
281
|
-
).resolves.toEqual({ committed: true });
|
|
282
|
-
await expect(
|
|
283
|
-
plugin.models.releases.findById(release.id),
|
|
284
|
-
).resolves.toBeNull();
|
|
285
|
-
await expect(plugin.models.bundles.findById(bundle.id)).resolves.toEqual(
|
|
286
|
-
bundle,
|
|
287
|
-
);
|
|
288
|
-
await expect(plugin.models.channels.list({})).resolves.toEqual({
|
|
289
|
-
channels: [],
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|