intellitester 0.1.12
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 +200 -0
- package/dist/chunk-35WJGNDA.cjs +136 -0
- package/dist/chunk-35WJGNDA.cjs.map +1 -0
- package/dist/chunk-4B54JUOP.js +234 -0
- package/dist/chunk-4B54JUOP.js.map +1 -0
- package/dist/chunk-5LFSLMQ7.js +2517 -0
- package/dist/chunk-5LFSLMQ7.js.map +1 -0
- package/dist/chunk-6PYKWWH5.js +63 -0
- package/dist/chunk-6PYKWWH5.js.map +1 -0
- package/dist/chunk-ARJYJVRM.cjs +302 -0
- package/dist/chunk-ARJYJVRM.cjs.map +1 -0
- package/dist/chunk-CN6HSJJX.js +133 -0
- package/dist/chunk-CN6HSJJX.js.map +1 -0
- package/dist/chunk-DE5UFTTG.js +31 -0
- package/dist/chunk-DE5UFTTG.js.map +1 -0
- package/dist/chunk-ECBA4GJ3.js +287 -0
- package/dist/chunk-ECBA4GJ3.js.map +1 -0
- package/dist/chunk-OFXNJXMV.cjs +237 -0
- package/dist/chunk-OFXNJXMV.cjs.map +1 -0
- package/dist/chunk-PAKODOH4.cjs +66 -0
- package/dist/chunk-PAKODOH4.cjs.map +1 -0
- package/dist/chunk-QMYM2TCH.cjs +36 -0
- package/dist/chunk-QMYM2TCH.cjs.map +1 -0
- package/dist/chunk-SAVY6D3X.js +125 -0
- package/dist/chunk-SAVY6D3X.js.map +1 -0
- package/dist/chunk-UUJXCHVT.cjs +128 -0
- package/dist/chunk-UUJXCHVT.cjs.map +1 -0
- package/dist/chunk-XWGUA67E.cjs +2552 -0
- package/dist/chunk-XWGUA67E.cjs.map +1 -0
- package/dist/cli/index.cjs +1985 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1957 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/cleanup/index.cjs +45 -0
- package/dist/core/cleanup/index.cjs.map +1 -0
- package/dist/core/cleanup/index.d.cts +117 -0
- package/dist/core/cleanup/index.d.ts +117 -0
- package/dist/core/cleanup/index.js +8 -0
- package/dist/core/cleanup/index.js.map +1 -0
- package/dist/index.cjs +110 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +852 -0
- package/dist/index.d.ts +852 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.cjs +22 -0
- package/dist/integration/index.cjs.map +1 -0
- package/dist/integration/index.d.cts +42 -0
- package/dist/integration/index.d.ts +42 -0
- package/dist/integration/index.js +20 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/providers/appwrite/index.cjs +16 -0
- package/dist/providers/appwrite/index.cjs.map +1 -0
- package/dist/providers/appwrite/index.d.cts +12 -0
- package/dist/providers/appwrite/index.d.ts +12 -0
- package/dist/providers/appwrite/index.js +3 -0
- package/dist/providers/appwrite/index.js.map +1 -0
- package/dist/providers/index.cjs +60 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +13 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/mysql/index.cjs +16 -0
- package/dist/providers/mysql/index.cjs.map +1 -0
- package/dist/providers/mysql/index.d.cts +14 -0
- package/dist/providers/mysql/index.d.ts +14 -0
- package/dist/providers/mysql/index.js +3 -0
- package/dist/providers/mysql/index.js.map +1 -0
- package/dist/providers/postgres/index.cjs +16 -0
- package/dist/providers/postgres/index.cjs.map +1 -0
- package/dist/providers/postgres/index.d.cts +10 -0
- package/dist/providers/postgres/index.d.ts +10 -0
- package/dist/providers/postgres/index.js +3 -0
- package/dist/providers/postgres/index.js.map +1 -0
- package/dist/providers/sqlite/index.cjs +16 -0
- package/dist/providers/sqlite/index.cjs.map +1 -0
- package/dist/providers/sqlite/index.d.cts +11 -0
- package/dist/providers/sqlite/index.d.ts +11 -0
- package/dist/providers/sqlite/index.js +3 -0
- package/dist/providers/sqlite/index.js.map +1 -0
- package/dist/types-LONNVTIF.d.cts +56 -0
- package/dist/types-l-ZaFKC-.d.ts +56 -0
- package/package.json +114 -0
- package/schemas/intellitester.config.schema.json +384 -0
- package/schemas/test.schema.json +517 -0
- package/schemas/workflow.schema.json +227 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { providerFactories, getDefaultTypeMappings } from './chunk-DE5UFTTG.js';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import fs2 from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
async function loadCleanupHandlers(config, cwd = process.cwd()) {
|
|
8
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
9
|
+
let typeMappings = {};
|
|
10
|
+
let provider;
|
|
11
|
+
if (config.provider) {
|
|
12
|
+
const providerConfig = config[config.provider];
|
|
13
|
+
if (!providerConfig) {
|
|
14
|
+
throw new Error(`Provider "${config.provider}" specified but no configuration found`);
|
|
15
|
+
}
|
|
16
|
+
const factory = providerFactories[config.provider];
|
|
17
|
+
if (!factory) {
|
|
18
|
+
throw new Error(`Unknown provider: ${config.provider}. Available: ${Object.keys(providerFactories).join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
provider = factory(providerConfig);
|
|
21
|
+
await provider.configure(providerConfig);
|
|
22
|
+
for (const [methodName, handler] of Object.entries(provider.methods)) {
|
|
23
|
+
handlers.set(`${provider.name}.${methodName}`, handler);
|
|
24
|
+
}
|
|
25
|
+
typeMappings = { ...getDefaultTypeMappings(config.provider) };
|
|
26
|
+
}
|
|
27
|
+
const rootCleanupPath = path.join(cwd, "intellitester.cleanup.ts");
|
|
28
|
+
const rootHandlers = await tryLoadHandlerFile(rootCleanupPath);
|
|
29
|
+
if (rootHandlers) {
|
|
30
|
+
mergeHandlers(handlers, rootHandlers);
|
|
31
|
+
}
|
|
32
|
+
if (config.discover?.enabled !== false) {
|
|
33
|
+
const discoveryPaths = config.discover?.paths ?? ["./tests/cleanup"];
|
|
34
|
+
const pattern = config.discover?.pattern ?? "**/*.ts";
|
|
35
|
+
for (const basePath of discoveryPaths) {
|
|
36
|
+
const absoluteBase = path.isAbsolute(basePath) ? basePath : path.join(cwd, basePath);
|
|
37
|
+
try {
|
|
38
|
+
const files = await glob(pattern, {
|
|
39
|
+
cwd: absoluteBase,
|
|
40
|
+
absolute: true,
|
|
41
|
+
ignore: ["**/*.d.ts", "**/node_modules/**"]
|
|
42
|
+
});
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
const fileHandlers = await tryLoadHandlerFile(file);
|
|
45
|
+
if (fileHandlers) {
|
|
46
|
+
mergeHandlers(handlers, fileHandlers);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const handlerPath of config.handlers ?? []) {
|
|
54
|
+
const absolutePath = path.isAbsolute(handlerPath) ? handlerPath : path.join(cwd, handlerPath);
|
|
55
|
+
const fileHandlers = await tryLoadHandlerFile(absolutePath);
|
|
56
|
+
if (fileHandlers) {
|
|
57
|
+
mergeHandlers(handlers, fileHandlers);
|
|
58
|
+
} else {
|
|
59
|
+
console.warn(`Warning: Could not load cleanup handler file: ${handlerPath}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (config.types) {
|
|
63
|
+
typeMappings = { ...typeMappings, ...config.types };
|
|
64
|
+
}
|
|
65
|
+
return { handlers, typeMappings, provider };
|
|
66
|
+
}
|
|
67
|
+
async function tryLoadHandlerFile(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(filePath)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
let loadPath = filePath;
|
|
73
|
+
if (filePath.endsWith(".ts")) {
|
|
74
|
+
const jsPath = filePath.replace(/\.ts$/, ".js");
|
|
75
|
+
if (fs.existsSync(jsPath)) {
|
|
76
|
+
loadPath = jsPath;
|
|
77
|
+
} else {
|
|
78
|
+
loadPath = filePath;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const module = await import(`${loadPath}?t=${Date.now()}`);
|
|
82
|
+
if (module.default && typeof module.default === "object") {
|
|
83
|
+
return module.default;
|
|
84
|
+
}
|
|
85
|
+
const handlers = {};
|
|
86
|
+
for (const [key, value] of Object.entries(module)) {
|
|
87
|
+
if (typeof value === "function" && key !== "default") {
|
|
88
|
+
handlers[key] = value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Object.keys(handlers).length > 0 ? handlers : null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function mergeHandlers(target, source) {
|
|
97
|
+
for (const [key, handler] of Object.entries(source)) {
|
|
98
|
+
target.set(key, handler);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function resolveHandler(handlers, typeMappings, resourceType) {
|
|
102
|
+
const mappedKey = typeMappings[resourceType];
|
|
103
|
+
if (mappedKey && handlers.has(mappedKey)) {
|
|
104
|
+
return handlers.get(mappedKey);
|
|
105
|
+
}
|
|
106
|
+
if (handlers.has(resourceType)) {
|
|
107
|
+
return handlers.get(resourceType);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
var CLEANUP_DIR = ".intellitester/cleanup/failed";
|
|
112
|
+
async function saveFailedCleanup(cleanup, cwd = process.cwd()) {
|
|
113
|
+
const dir = path.join(cwd, CLEANUP_DIR);
|
|
114
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
115
|
+
const filePath = path.join(dir, `${cleanup.sessionId}.json`);
|
|
116
|
+
await fs2.writeFile(filePath, JSON.stringify(cleanup, null, 2), "utf8");
|
|
117
|
+
console.log(`Saved failed cleanup to ${filePath}`);
|
|
118
|
+
}
|
|
119
|
+
async function loadFailedCleanups(cwd = process.cwd()) {
|
|
120
|
+
const dir = path.join(cwd, CLEANUP_DIR);
|
|
121
|
+
try {
|
|
122
|
+
const files = await fs2.readdir(dir);
|
|
123
|
+
const cleanups = [];
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
if (file.endsWith(".json")) {
|
|
126
|
+
const content = await fs2.readFile(path.join(dir, file), "utf8");
|
|
127
|
+
cleanups.push(JSON.parse(content));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return cleanups;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error.code === "ENOENT") {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function removeFailedCleanup(sessionId, cwd = process.cwd()) {
|
|
139
|
+
const filePath = path.join(cwd, CLEANUP_DIR, `${sessionId}.json`);
|
|
140
|
+
try {
|
|
141
|
+
await fs2.unlink(filePath);
|
|
142
|
+
console.log(`Removed failed cleanup file: ${sessionId}.json`);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.code !== "ENOENT") {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/core/cleanup/executor.ts
|
|
151
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
152
|
+
async function executeCleanup(resources, handlers, typeMappings, options = {}) {
|
|
153
|
+
const { parallel = false, retries = 3, config, provider } = options;
|
|
154
|
+
const deleted = [];
|
|
155
|
+
const failed = [];
|
|
156
|
+
const sorted = [...resources].sort((a, b) => {
|
|
157
|
+
const aCreatedAt = "createdAt" in a ? a.createdAt : void 0;
|
|
158
|
+
const bCreatedAt = "createdAt" in b ? b.createdAt : void 0;
|
|
159
|
+
const timeA = aCreatedAt ? new Date(aCreatedAt).getTime() : 0;
|
|
160
|
+
const timeB = bCreatedAt ? new Date(bCreatedAt).getTime() : 0;
|
|
161
|
+
return timeB - timeA;
|
|
162
|
+
});
|
|
163
|
+
const deleteResource = async (resource) => {
|
|
164
|
+
const resourceLabel = `${resource.type}:${resource.id}`;
|
|
165
|
+
const isDeleted = "deleted" in resource ? resource.deleted : false;
|
|
166
|
+
if (isDeleted) {
|
|
167
|
+
deleted.push(`${resourceLabel} (already deleted)`);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const handler = resolveHandler(handlers, typeMappings, resource.type);
|
|
171
|
+
if (!handler) {
|
|
172
|
+
failed.push(`${resourceLabel} (no handler for type "${resource.type}")`);
|
|
173
|
+
console.warn(`No cleanup handler for resource type: ${resource.type}`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
177
|
+
try {
|
|
178
|
+
await handler(resource);
|
|
179
|
+
deleted.push(resourceLabel);
|
|
180
|
+
return true;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
183
|
+
if (attempt === retries) {
|
|
184
|
+
failed.push(`${resourceLabel} (${errorMessage})`);
|
|
185
|
+
console.warn(
|
|
186
|
+
`Failed to delete ${resourceLabel} after ${retries} attempts:`,
|
|
187
|
+
errorMessage
|
|
188
|
+
);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const delay = 100 * Math.pow(2, attempt - 1);
|
|
192
|
+
console.debug(
|
|
193
|
+
`Cleanup attempt ${attempt}/${retries} failed for ${resourceLabel}, retrying in ${delay}ms...`
|
|
194
|
+
);
|
|
195
|
+
await sleep(delay);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
if (parallel) {
|
|
201
|
+
const results = await Promise.allSettled(sorted.map(deleteResource));
|
|
202
|
+
results.forEach((result2, index) => {
|
|
203
|
+
if (result2.status === "rejected") {
|
|
204
|
+
const resource = sorted[index];
|
|
205
|
+
console.error(
|
|
206
|
+
`Unexpected error cleaning up ${resource.type}:${resource.id}:`,
|
|
207
|
+
result2.reason
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
for (const resource of sorted) {
|
|
213
|
+
await deleteResource(resource);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (config?.scanUntracked && provider?.cleanupUntracked) {
|
|
217
|
+
console.log("\n[Cleanup] Scanning for untracked resources...");
|
|
218
|
+
try {
|
|
219
|
+
const untrackedResult = await provider.cleanupUntracked({
|
|
220
|
+
testStartTime: options.testStartTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
221
|
+
userId: options.userId,
|
|
222
|
+
sessionId: options.sessionId
|
|
223
|
+
});
|
|
224
|
+
if (untrackedResult.deleted.length > 0) {
|
|
225
|
+
console.log(`[Cleanup] Cleaned up ${untrackedResult.deleted.length} untracked resources`);
|
|
226
|
+
deleted.push(...untrackedResult.deleted);
|
|
227
|
+
}
|
|
228
|
+
if (untrackedResult.failed.length > 0) {
|
|
229
|
+
console.log(`[Cleanup] Failed to clean up ${untrackedResult.failed.length} untracked resources`);
|
|
230
|
+
failed.push(...untrackedResult.failed);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
234
|
+
console.warn(`[Cleanup] Untracked resource scan failed: ${errorMessage}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const result = {
|
|
238
|
+
success: failed.length === 0,
|
|
239
|
+
deleted,
|
|
240
|
+
failed
|
|
241
|
+
};
|
|
242
|
+
if (failed.length > 0 && options.sessionId && options.providerConfig) {
|
|
243
|
+
try {
|
|
244
|
+
const failedResources = sorted.filter((resource) => {
|
|
245
|
+
const resourceLabel = `${resource.type}:${resource.id}`;
|
|
246
|
+
return failed.some((f) => f.startsWith(resourceLabel));
|
|
247
|
+
});
|
|
248
|
+
await saveFailedCleanup(
|
|
249
|
+
{
|
|
250
|
+
sessionId: options.sessionId,
|
|
251
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
252
|
+
resources: failedResources,
|
|
253
|
+
providerConfig: options.providerConfig,
|
|
254
|
+
errors: failed
|
|
255
|
+
},
|
|
256
|
+
options.cwd
|
|
257
|
+
);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.warn("Failed to save cleanup persistence file:", error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
function createCleanupExecutor(handlers, typeMappings, options = {}) {
|
|
265
|
+
return {
|
|
266
|
+
/**
|
|
267
|
+
* Execute cleanup for resources
|
|
268
|
+
*/
|
|
269
|
+
cleanup: (resources) => executeCleanup(resources, handlers, typeMappings, options),
|
|
270
|
+
/**
|
|
271
|
+
* Execute cleanup for a single resource
|
|
272
|
+
*/
|
|
273
|
+
cleanupOne: async (resource) => {
|
|
274
|
+
const result = await executeCleanup([resource], handlers, typeMappings, options);
|
|
275
|
+
return result.success;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/core/cleanup/index.ts
|
|
281
|
+
function defineCleanupHandlers(handlers) {
|
|
282
|
+
return handlers;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export { createCleanupExecutor, defineCleanupHandlers, executeCleanup, loadCleanupHandlers, loadFailedCleanups, removeFailedCleanup, resolveHandler, saveFailedCleanup };
|
|
286
|
+
//# sourceMappingURL=chunk-ECBA4GJ3.js.map
|
|
287
|
+
//# sourceMappingURL=chunk-ECBA4GJ3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/cleanup/loader.ts","../src/core/cleanup/persistence.ts","../src/core/cleanup/executor.ts","../src/core/cleanup/index.ts"],"names":["path","fs","result"],"mappings":"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","file":"chunk-ECBA4GJ3.js","sourcesContent":["import { glob } from 'glob';\nimport path from 'path';\nimport fs from 'fs';\nimport type { CleanupHandler, CleanupConfig, CleanupProvider } from './types.js';\nimport { providerFactories, getDefaultTypeMappings } from '../../providers/index.js';\n\n/**\n * Load all cleanup handlers based on configuration\n *\n * Loading order (later overrides earlier):\n * 1. Built-in provider methods\n * 2. intellitester.cleanup.ts at project root\n * 3. Discovery paths (default: tests/cleanup/**\\/*.ts)\n * 4. Explicit handler files from config\n *\n * @param config - Cleanup configuration object\n * @param cwd - Current working directory (defaults to process.cwd())\n *\n * @example\n * ```typescript\n * // Basic usage with a provider\n * const { handlers, typeMappings } = await loadCleanupHandlers({\n * provider: 'sqlite',\n * sqlite: { database: './test.db' },\n * types: {\n * user: 'sqlite.deleteUser',\n * team: 'sqlite.deleteRow'\n * }\n * });\n *\n * // With custom discovery paths\n * const { handlers, typeMappings } = await loadCleanupHandlers({\n * provider: 'postgres',\n * postgres: { connectionString: 'postgresql://...' },\n * discover: {\n * enabled: true,\n * paths: ['./tests/cleanup', './e2e/cleanup'],\n * pattern: '**\\/*.cleanup.ts'\n * },\n * handlers: ['./custom-cleanup.ts']\n * });\n * ```\n *\n * @returns Object containing loaded handlers map and type mappings\n */\nexport async function loadCleanupHandlers(\n config: CleanupConfig,\n cwd: string = process.cwd()\n): Promise<{\n handlers: Map<string, CleanupHandler>;\n typeMappings: Record<string, string>;\n provider?: CleanupProvider;\n}> {\n const handlers = new Map<string, CleanupHandler>();\n let typeMappings: Record<string, string> = {};\n let provider: CleanupProvider | undefined;\n\n // 1. Load built-in provider methods\n if (config.provider) {\n const providerConfig = config[config.provider] as Record<string, unknown> | undefined;\n if (!providerConfig) {\n throw new Error(`Provider \"${config.provider}\" specified but no configuration found`);\n }\n\n const factory = providerFactories[config.provider];\n if (!factory) {\n throw new Error(`Unknown provider: ${config.provider}. Available: ${Object.keys(providerFactories).join(', ')}`);\n }\n\n provider = factory(providerConfig);\n await provider.configure(providerConfig);\n\n // Register provider methods\n for (const [methodName, handler] of Object.entries(provider.methods)) {\n handlers.set(`${provider.name}.${methodName}`, handler);\n }\n\n // Get default type mappings for this provider\n typeMappings = { ...getDefaultTypeMappings(config.provider) };\n }\n\n // 2. Auto-discover intellitester.cleanup.ts at root\n const rootCleanupPath = path.join(cwd, 'intellitester.cleanup.ts');\n const rootHandlers = await tryLoadHandlerFile(rootCleanupPath);\n if (rootHandlers) {\n mergeHandlers(handlers, rootHandlers);\n }\n\n // 3. Auto-discover from discovery paths\n if (config.discover?.enabled !== false) {\n const discoveryPaths = config.discover?.paths ?? ['./tests/cleanup'];\n const pattern = config.discover?.pattern ?? '**/*.ts';\n\n for (const basePath of discoveryPaths) {\n const absoluteBase = path.isAbsolute(basePath) ? basePath : path.join(cwd, basePath);\n\n try {\n const files = await glob(pattern, {\n cwd: absoluteBase,\n absolute: true,\n ignore: ['**/*.d.ts', '**/node_modules/**']\n });\n\n for (const file of files) {\n const fileHandlers = await tryLoadHandlerFile(file);\n if (fileHandlers) {\n mergeHandlers(handlers, fileHandlers);\n }\n }\n } catch {\n // Directory doesn't exist or no matches - that's fine\n }\n }\n }\n\n // 4. Load explicit handler files\n for (const handlerPath of config.handlers ?? []) {\n const absolutePath = path.isAbsolute(handlerPath)\n ? handlerPath\n : path.join(cwd, handlerPath);\n\n const fileHandlers = await tryLoadHandlerFile(absolutePath);\n if (fileHandlers) {\n mergeHandlers(handlers, fileHandlers);\n } else {\n console.warn(`Warning: Could not load cleanup handler file: ${handlerPath}`);\n }\n }\n\n // Merge config.types over default mappings\n if (config.types) {\n typeMappings = { ...typeMappings, ...config.types };\n }\n\n return { handlers, typeMappings, provider };\n}\n\n/**\n * Try to load a handler file, returning null if it doesn't exist or fails\n */\nasync function tryLoadHandlerFile(filePath: string): Promise<Record<string, CleanupHandler> | null> {\n try {\n // Check if file exists\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n // For TypeScript files, we need to handle them appropriately\n // In a built environment, look for the .js equivalent\n let loadPath = filePath;\n if (filePath.endsWith('.ts')) {\n // In production, TypeScript files will be compiled to .js\n // Try to load the compiled .js version from dist if running compiled\n const jsPath = filePath.replace(/\\.ts$/, '.js');\n if (fs.existsSync(jsPath)) {\n loadPath = jsPath;\n } else {\n // Running in development with tsx/ts-node\n // Try direct import which works with these tools\n loadPath = filePath;\n }\n }\n\n // Use dynamic import with cache busting for fresh load\n const module = await import(`${loadPath}?t=${Date.now()}`);\n\n // Handle default export or named exports\n if (module.default && typeof module.default === 'object') {\n return module.default as Record<string, CleanupHandler>;\n }\n\n // Filter to only include function exports\n const handlers: Record<string, CleanupHandler> = {};\n for (const [key, value] of Object.entries(module)) {\n if (typeof value === 'function' && key !== 'default') {\n handlers[key] = value as CleanupHandler;\n }\n }\n\n return Object.keys(handlers).length > 0 ? handlers : null;\n } catch {\n // File doesn't exist or failed to load\n return null;\n }\n}\n\n/**\n * Merge handlers from a file into the main handlers map\n */\nfunction mergeHandlers(\n target: Map<string, CleanupHandler>,\n source: Record<string, CleanupHandler>\n): void {\n for (const [key, handler] of Object.entries(source)) {\n target.set(key, handler);\n }\n}\n\n/**\n * Get a handler by key, checking both direct keys and provider.method format\n */\nexport function resolveHandler(\n handlers: Map<string, CleanupHandler>,\n typeMappings: Record<string, string>,\n resourceType: string\n): CleanupHandler | null {\n // First check type mappings\n const mappedKey = typeMappings[resourceType];\n if (mappedKey && handlers.has(mappedKey)) {\n return handlers.get(mappedKey)!;\n }\n\n // Then check for direct handler\n if (handlers.has(resourceType)) {\n return handlers.get(resourceType)!;\n }\n\n return null;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { TrackedResource } from '../../integration/index.js';\n\n// Types\nexport interface FailedCleanup {\n sessionId: string;\n timestamp: string;\n resources: TrackedResource[];\n // Generic provider config (no secrets!)\n providerConfig: {\n provider: string; // 'appwrite' | 'postgres' | 'mysql' | 'sqlite'\n [key: string]: unknown; // endpoint, projectId, database, etc. - NO passwords/keys\n };\n errors: string[];\n}\n\nconst CLEANUP_DIR = '.intellitester/cleanup/failed';\n\n// Save failed cleanup to disk\nexport async function saveFailedCleanup(\n cleanup: FailedCleanup,\n cwd: string = process.cwd()\n): Promise<void> {\n const dir = path.join(cwd, CLEANUP_DIR);\n await fs.mkdir(dir, { recursive: true });\n const filePath = path.join(dir, `${cleanup.sessionId}.json`);\n await fs.writeFile(filePath, JSON.stringify(cleanup, null, 2), 'utf8');\n console.log(`Saved failed cleanup to ${filePath}`);\n}\n\n// Load all failed cleanups from disk\nexport async function loadFailedCleanups(\n cwd: string = process.cwd()\n): Promise<FailedCleanup[]> {\n const dir = path.join(cwd, CLEANUP_DIR);\n try {\n const files = await fs.readdir(dir);\n const cleanups: FailedCleanup[] = [];\n for (const file of files) {\n if (file.endsWith('.json')) {\n const content = await fs.readFile(path.join(dir, file), 'utf8');\n cleanups.push(JSON.parse(content));\n }\n }\n return cleanups;\n } catch (error) {\n // Directory doesn't exist = no failed cleanups\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return [];\n }\n throw error;\n }\n}\n\n// Remove a failed cleanup file after successful retry\nexport async function removeFailedCleanup(\n sessionId: string,\n cwd: string = process.cwd()\n): Promise<void> {\n const filePath = path.join(cwd, CLEANUP_DIR, `${sessionId}.json`);\n try {\n await fs.unlink(filePath);\n console.log(`Removed failed cleanup file: ${sessionId}.json`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n}\n","import type { TrackedResource } from '../../integration/index.js';\nimport type { CleanupHandler, CleanupResult, ExecutorOptions, CleanupConfig, CleanupProvider } from './types.js';\nimport { resolveHandler } from './loader.js';\nimport { saveFailedCleanup } from './persistence.js';\n\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));\n\n/**\n * Extended options for cleanup execution including provider and config\n */\nexport interface ExtendedCleanupOptions extends ExecutorOptions {\n config?: CleanupConfig;\n provider?: CleanupProvider;\n}\n\n/**\n * Execute cleanup for all tracked resources\n *\n * @param resources - Array of tracked resources to clean up\n * @param handlers - Map of handler names to cleanup functions\n * @param typeMappings - Map of resource types to handler names\n * @param options - Executor options (parallel, retries, config, provider)\n */\nexport async function executeCleanup(\n resources: TrackedResource[],\n handlers: Map<string, CleanupHandler>,\n typeMappings: Record<string, string>,\n options: ExtendedCleanupOptions = {}\n): Promise<CleanupResult> {\n const { parallel = false, retries = 3, config, provider } = options;\n const deleted: string[] = [];\n const failed: string[] = [];\n\n // Sort by creation time (reverse) - delete newest first (LIFO)\n const sorted = [...resources].sort((a, b) => {\n // Handle optional createdAt field (present at runtime from tracking server)\n const aCreatedAt = 'createdAt' in a ? (a.createdAt as string | undefined) : undefined;\n const bCreatedAt = 'createdAt' in b ? (b.createdAt as string | undefined) : undefined;\n\n const timeA = aCreatedAt ? new Date(aCreatedAt).getTime() : 0;\n const timeB = bCreatedAt ? new Date(bCreatedAt).getTime() : 0;\n\n return timeB - timeA;\n });\n\n const deleteResource = async (resource: TrackedResource): Promise<boolean> => {\n const resourceLabel = `${resource.type}:${resource.id}`;\n\n // Skip already deleted resources (deleted field may be set dynamically)\n const isDeleted = 'deleted' in resource ? (resource.deleted as boolean | undefined) : false;\n if (isDeleted) {\n deleted.push(`${resourceLabel} (already deleted)`);\n return true;\n }\n\n // Resolve the handler\n const handler = resolveHandler(handlers, typeMappings, resource.type);\n\n if (!handler) {\n failed.push(`${resourceLabel} (no handler for type \"${resource.type}\")`);\n console.warn(`No cleanup handler for resource type: ${resource.type}`);\n return false;\n }\n\n // Retry with exponential backoff\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n await handler(resource);\n deleted.push(resourceLabel);\n return true;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (attempt === retries) {\n failed.push(`${resourceLabel} (${errorMessage})`);\n console.warn(\n `Failed to delete ${resourceLabel} after ${retries} attempts:`,\n errorMessage\n );\n return false;\n }\n\n // Exponential backoff: 100ms, 200ms, 400ms, 800ms...\n const delay = 100 * Math.pow(2, attempt - 1);\n console.debug(\n `Cleanup attempt ${attempt}/${retries} failed for ${resourceLabel}, ` +\n `retrying in ${delay}ms...`\n );\n await sleep(delay);\n }\n }\n\n return false;\n };\n\n if (parallel) {\n // Parallel execution\n const results = await Promise.allSettled(sorted.map(deleteResource));\n\n // Log any unexpected rejections (shouldn't happen since deleteResource handles errors)\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const resource = sorted[index];\n console.error(\n `Unexpected error cleaning up ${resource.type}:${resource.id}:`,\n result.reason\n );\n }\n });\n } else {\n // Sequential execution\n for (const resource of sorted) {\n await deleteResource(resource);\n }\n }\n\n // After tracked cleanup, scan for untracked resources if configured\n if (config?.scanUntracked && provider?.cleanupUntracked) {\n console.log('\\n[Cleanup] Scanning for untracked resources...');\n\n try {\n const untrackedResult = await provider.cleanupUntracked({\n testStartTime: options.testStartTime ?? new Date().toISOString(),\n userId: options.userId,\n sessionId: options.sessionId,\n });\n\n if (untrackedResult.deleted.length > 0) {\n console.log(`[Cleanup] Cleaned up ${untrackedResult.deleted.length} untracked resources`);\n deleted.push(...untrackedResult.deleted);\n }\n\n if (untrackedResult.failed.length > 0) {\n console.log(`[Cleanup] Failed to clean up ${untrackedResult.failed.length} untracked resources`);\n failed.push(...untrackedResult.failed);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.warn(`[Cleanup] Untracked resource scan failed: ${errorMessage}`);\n }\n }\n\n const result: CleanupResult = {\n success: failed.length === 0,\n deleted,\n failed,\n };\n\n // Save failed cleanups for retry if there were failures\n if (failed.length > 0 && options.sessionId && options.providerConfig) {\n try {\n // Extract the resources that failed\n const failedResources = sorted.filter((resource) => {\n const resourceLabel = `${resource.type}:${resource.id}`;\n return failed.some((f) => f.startsWith(resourceLabel));\n });\n\n await saveFailedCleanup(\n {\n sessionId: options.sessionId,\n timestamp: new Date().toISOString(),\n resources: failedResources,\n providerConfig: options.providerConfig,\n errors: failed,\n },\n options.cwd\n );\n } catch (error) {\n // Don't fail the cleanup if we can't save the failed cleanup file\n console.warn('Failed to save cleanup persistence file:', error);\n }\n }\n\n return result;\n}\n\n/**\n * Create a cleanup executor with pre-configured handlers and options\n */\nexport function createCleanupExecutor(\n handlers: Map<string, CleanupHandler>,\n typeMappings: Record<string, string>,\n options: ExecutorOptions = {}\n) {\n return {\n /**\n * Execute cleanup for resources\n */\n cleanup: (resources: TrackedResource[]) =>\n executeCleanup(resources, handlers, typeMappings, options),\n\n /**\n * Execute cleanup for a single resource\n */\n cleanupOne: async (resource: TrackedResource): Promise<boolean> => {\n const result = await executeCleanup([resource], handlers, typeMappings, options);\n return result.success;\n },\n };\n}\n","export * from './types.js';\nimport type { CleanupHandler } from './types.js';\n\nexport { executeCleanup, createCleanupExecutor, type ExtendedCleanupOptions } from './executor.js';\nexport { loadCleanupHandlers, resolveHandler } from './loader.js';\nexport { saveFailedCleanup, loadFailedCleanups, removeFailedCleanup, type FailedCleanup } from './persistence.js';\n\n/**\n * Helper for defining cleanup handlers in user files.\n * Provides type safety for custom cleanup handler definitions.\n *\n * @example\n * // In cleanup.ts\n * import { defineCleanupHandlers } from 'intellitester/cleanup';\n *\n * export default defineCleanupHandlers({\n * async deleteUser(resource) {\n * // cleanup logic\n * },\n * async deleteTeam(resource) {\n * // cleanup logic\n * }\n * });\n */\nexport function defineCleanupHandlers<T extends Record<string, CleanupHandler>>(handlers: T): T {\n return handlers;\n}\n"]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var nodeAppwrite = require('node-appwrite');
|
|
4
|
+
|
|
5
|
+
// src/providers/appwrite/index.ts
|
|
6
|
+
function createAppwriteProvider(config) {
|
|
7
|
+
const client = new nodeAppwrite.Client().setEndpoint(config.endpoint).setProject(config.projectId).setKey(config.apiKey);
|
|
8
|
+
const tablesDB = new nodeAppwrite.TablesDB(client);
|
|
9
|
+
const storage = new nodeAppwrite.Storage(client);
|
|
10
|
+
const teams = new nodeAppwrite.Teams(client);
|
|
11
|
+
const users = new nodeAppwrite.Users(client);
|
|
12
|
+
const methods = {
|
|
13
|
+
deleteRow: async (resource) => {
|
|
14
|
+
const databaseId = resource.databaseId;
|
|
15
|
+
const tableId = resource.tableId;
|
|
16
|
+
if (!databaseId || !tableId) {
|
|
17
|
+
throw new Error(`Missing databaseId or tableId for row ${resource.id}`);
|
|
18
|
+
}
|
|
19
|
+
await tablesDB.deleteRow({
|
|
20
|
+
databaseId,
|
|
21
|
+
tableId,
|
|
22
|
+
rowId: resource.id
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
deleteFile: async (resource) => {
|
|
26
|
+
const bucketId = resource.bucketId;
|
|
27
|
+
if (!bucketId) {
|
|
28
|
+
throw new Error(`Missing bucketId for file ${resource.id}`);
|
|
29
|
+
}
|
|
30
|
+
await storage.deleteFile(bucketId, resource.id);
|
|
31
|
+
},
|
|
32
|
+
deleteTeam: async (resource) => {
|
|
33
|
+
await teams.delete(resource.id);
|
|
34
|
+
},
|
|
35
|
+
deleteUser: async (resource) => {
|
|
36
|
+
await users.delete(resource.id);
|
|
37
|
+
},
|
|
38
|
+
deleteMembership: async (resource) => {
|
|
39
|
+
const teamId = resource.teamId;
|
|
40
|
+
if (!teamId) {
|
|
41
|
+
throw new Error(`Missing teamId for membership ${resource.id}`);
|
|
42
|
+
}
|
|
43
|
+
await teams.deleteMembership(teamId, resource.id);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
async function cleanupUntracked(options) {
|
|
47
|
+
const { testStartTime, userId, sessionId } = options;
|
|
48
|
+
const deleted = [];
|
|
49
|
+
const failed = [];
|
|
50
|
+
let scanned = 0;
|
|
51
|
+
console.log(
|
|
52
|
+
`[Appwrite Cleanup] Starting untracked cleanup for session ${sessionId || "unknown"}`
|
|
53
|
+
);
|
|
54
|
+
console.log(`[Appwrite Cleanup] Test start time: ${testStartTime}`);
|
|
55
|
+
console.log(`[Appwrite Cleanup] User ID to match: ${userId || "none"}`);
|
|
56
|
+
try {
|
|
57
|
+
const databases = await tablesDB.list();
|
|
58
|
+
console.log(
|
|
59
|
+
`[Appwrite Cleanup] Found ${databases.databases.length} databases to scan`
|
|
60
|
+
);
|
|
61
|
+
for (const db of databases.databases) {
|
|
62
|
+
const tables = await tablesDB.listTables({ databaseId: db.$id });
|
|
63
|
+
console.log(
|
|
64
|
+
`[Appwrite Cleanup] Database "${db.name}" (${db.$id}): ${tables.tables.length} tables`
|
|
65
|
+
);
|
|
66
|
+
for (const table of tables.tables) {
|
|
67
|
+
if (table.name.startsWith("_intellitester")) {
|
|
68
|
+
console.log(
|
|
69
|
+
`[Appwrite Cleanup] Skipping tracking table: ${table.name}`
|
|
70
|
+
);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
scanned++;
|
|
74
|
+
try {
|
|
75
|
+
let hasMore = true;
|
|
76
|
+
let cursor;
|
|
77
|
+
while (hasMore) {
|
|
78
|
+
const queries = [
|
|
79
|
+
nodeAppwrite.Query.greaterThanEqual("$createdAt", testStartTime),
|
|
80
|
+
nodeAppwrite.Query.limit(100)
|
|
81
|
+
];
|
|
82
|
+
if (cursor) {
|
|
83
|
+
queries.push(nodeAppwrite.Query.cursorAfter(cursor));
|
|
84
|
+
}
|
|
85
|
+
const rows = await tablesDB.listRows({
|
|
86
|
+
databaseId: db.$id,
|
|
87
|
+
tableId: table.$id,
|
|
88
|
+
queries
|
|
89
|
+
});
|
|
90
|
+
console.log(
|
|
91
|
+
`[Appwrite Cleanup] Table "${table.name}": found ${rows.rows.length} rows created after test start`
|
|
92
|
+
);
|
|
93
|
+
for (const row of rows.rows) {
|
|
94
|
+
const rowJson = JSON.stringify(row);
|
|
95
|
+
const shouldDelete = userId && rowJson.includes(userId);
|
|
96
|
+
if (shouldDelete) {
|
|
97
|
+
try {
|
|
98
|
+
await tablesDB.deleteRow({
|
|
99
|
+
databaseId: db.$id,
|
|
100
|
+
tableId: table.$id,
|
|
101
|
+
rowId: row.$id
|
|
102
|
+
});
|
|
103
|
+
deleted.push(`row:${db.$id}/${table.$id}/${row.$id}`);
|
|
104
|
+
console.log(
|
|
105
|
+
`[Appwrite Cleanup] Deleted row ${row.$id} from ${table.name}`
|
|
106
|
+
);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
failed.push(`row:${db.$id}/${table.$id}/${row.$id}`);
|
|
109
|
+
console.warn(
|
|
110
|
+
`[Appwrite Cleanup] Failed to delete row ${row.$id}:`,
|
|
111
|
+
error
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (rows.rows.length < 100) {
|
|
117
|
+
hasMore = false;
|
|
118
|
+
} else {
|
|
119
|
+
cursor = rows.rows[rows.rows.length - 1].$id;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`[Appwrite Cleanup] Error scanning table ${table.name}:`,
|
|
125
|
+
error
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log("[Appwrite Cleanup] Scanning storage buckets...");
|
|
131
|
+
const buckets = await storage.listBuckets();
|
|
132
|
+
console.log(
|
|
133
|
+
`[Appwrite Cleanup] Found ${buckets.buckets.length} buckets to scan`
|
|
134
|
+
);
|
|
135
|
+
for (const bucket of buckets.buckets) {
|
|
136
|
+
scanned++;
|
|
137
|
+
try {
|
|
138
|
+
let hasMore = true;
|
|
139
|
+
let cursor;
|
|
140
|
+
while (hasMore) {
|
|
141
|
+
const queries = [
|
|
142
|
+
nodeAppwrite.Query.greaterThanEqual("$createdAt", testStartTime),
|
|
143
|
+
nodeAppwrite.Query.limit(100)
|
|
144
|
+
];
|
|
145
|
+
if (cursor) {
|
|
146
|
+
queries.push(nodeAppwrite.Query.cursorAfter(cursor));
|
|
147
|
+
}
|
|
148
|
+
const files = await storage.listFiles({
|
|
149
|
+
bucketId: bucket.$id,
|
|
150
|
+
queries
|
|
151
|
+
});
|
|
152
|
+
console.log(
|
|
153
|
+
`[Appwrite Cleanup] Bucket "${bucket.name}": found ${files.files.length} files created after test start`
|
|
154
|
+
);
|
|
155
|
+
for (const file of files.files) {
|
|
156
|
+
const fileRecord = file;
|
|
157
|
+
const createdBy = fileRecord.$createdBy;
|
|
158
|
+
const shouldDelete = userId && (createdBy === userId || file.name.includes(userId));
|
|
159
|
+
if (shouldDelete) {
|
|
160
|
+
try {
|
|
161
|
+
await storage.deleteFile({
|
|
162
|
+
bucketId: bucket.$id,
|
|
163
|
+
fileId: file.$id
|
|
164
|
+
});
|
|
165
|
+
deleted.push(`file:${bucket.$id}/${file.$id}`);
|
|
166
|
+
console.log(
|
|
167
|
+
`[Appwrite Cleanup] Deleted file ${file.$id} from bucket ${bucket.name}`
|
|
168
|
+
);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
failed.push(`file:${bucket.$id}/${file.$id}`);
|
|
171
|
+
console.warn(
|
|
172
|
+
`[Appwrite Cleanup] Failed to delete file ${file.$id}:`,
|
|
173
|
+
error
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (files.files.length < 100) {
|
|
179
|
+
hasMore = false;
|
|
180
|
+
} else {
|
|
181
|
+
cursor = files.files[files.files.length - 1].$id;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.warn(
|
|
186
|
+
`[Appwrite Cleanup] Error scanning bucket ${bucket.name}:`,
|
|
187
|
+
error
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (userId) {
|
|
192
|
+
console.log(`[Appwrite Cleanup] Deleting test user: ${userId}`);
|
|
193
|
+
try {
|
|
194
|
+
await users.delete(userId);
|
|
195
|
+
deleted.push(`user:${userId}`);
|
|
196
|
+
console.log(`[Appwrite Cleanup] Deleted user ${userId}`);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
failed.push(`user:${userId}`);
|
|
199
|
+
console.warn(
|
|
200
|
+
`[Appwrite Cleanup] Failed to delete user ${userId}:`,
|
|
201
|
+
error
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("[Appwrite Cleanup] Error during cleanup scan:", error);
|
|
207
|
+
}
|
|
208
|
+
console.log(
|
|
209
|
+
`[Appwrite Cleanup] Cleanup complete. Scanned: ${scanned}, Deleted: ${deleted.length}, Failed: ${failed.length}`
|
|
210
|
+
);
|
|
211
|
+
return {
|
|
212
|
+
success: failed.length === 0,
|
|
213
|
+
scanned,
|
|
214
|
+
deleted,
|
|
215
|
+
failed
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
name: "appwrite",
|
|
220
|
+
async configure() {
|
|
221
|
+
},
|
|
222
|
+
methods,
|
|
223
|
+
cleanupUntracked
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
var appwriteTypeMappings = {
|
|
227
|
+
row: "appwrite.deleteRow",
|
|
228
|
+
file: "appwrite.deleteFile",
|
|
229
|
+
team: "appwrite.deleteTeam",
|
|
230
|
+
user: "appwrite.deleteUser",
|
|
231
|
+
membership: "appwrite.deleteMembership"
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
exports.appwriteTypeMappings = appwriteTypeMappings;
|
|
235
|
+
exports.createAppwriteProvider = createAppwriteProvider;
|
|
236
|
+
//# sourceMappingURL=chunk-OFXNJXMV.cjs.map
|
|
237
|
+
//# sourceMappingURL=chunk-OFXNJXMV.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/appwrite/index.ts"],"names":["Client","TablesDB","Storage","Teams","Users","Query"],"mappings":"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","file":"chunk-OFXNJXMV.cjs","sourcesContent":["import { Client, Users, TablesDB, Storage, Teams, Query } from 'node-appwrite';\nimport type {\n CleanupProvider,\n CleanupHandler,\n CleanupUntrackedOptions,\n CleanupUntrackedResult,\n} from '../../core/cleanup/types.js';\nimport type { TrackedResource } from '../../integration/index.js';\n\ninterface AppwriteConfig {\n endpoint: string;\n projectId: string;\n apiKey: string;\n}\n\nexport function createAppwriteProvider(config: AppwriteConfig): CleanupProvider {\n const client = new Client()\n .setEndpoint(config.endpoint)\n .setProject(config.projectId)\n .setKey(config.apiKey);\n\n const tablesDB = new TablesDB(client);\n const storage = new Storage(client);\n const teams = new Teams(client);\n const users = new Users(client);\n\n const methods: Record<string, CleanupHandler> = {\n deleteRow: async (resource: TrackedResource) => {\n const databaseId = resource.databaseId as string;\n const tableId = resource.tableId as string;\n\n if (!databaseId || !tableId) {\n throw new Error(`Missing databaseId or tableId for row ${resource.id}`);\n }\n\n await tablesDB.deleteRow({\n databaseId,\n tableId,\n rowId: resource.id,\n });\n },\n\n deleteFile: async (resource: TrackedResource) => {\n const bucketId = resource.bucketId as string;\n\n if (!bucketId) {\n throw new Error(`Missing bucketId for file ${resource.id}`);\n }\n\n await storage.deleteFile(bucketId, resource.id);\n },\n\n deleteTeam: async (resource: TrackedResource) => {\n await teams.delete(resource.id);\n },\n\n deleteUser: async (resource: TrackedResource) => {\n await users.delete(resource.id);\n },\n\n deleteMembership: async (resource: TrackedResource) => {\n const teamId = resource.teamId as string;\n\n if (!teamId) {\n throw new Error(`Missing teamId for membership ${resource.id}`);\n }\n\n await teams.deleteMembership(teamId, resource.id);\n },\n };\n\n /**\n * Scan all Appwrite tables for resources created after testStartTime\n * that contain the userId in any field, and delete them.\n */\n async function cleanupUntracked(\n options: CleanupUntrackedOptions\n ): Promise<CleanupUntrackedResult> {\n const { testStartTime, userId, sessionId } = options;\n const deleted: string[] = [];\n const failed: string[] = [];\n let scanned = 0;\n\n console.log(\n `[Appwrite Cleanup] Starting untracked cleanup for session ${sessionId || 'unknown'}`\n );\n console.log(`[Appwrite Cleanup] Test start time: ${testStartTime}`);\n console.log(`[Appwrite Cleanup] User ID to match: ${userId || 'none'}`);\n\n try {\n // 1. List all databases\n const databases = await tablesDB.list();\n console.log(\n `[Appwrite Cleanup] Found ${databases.databases.length} databases to scan`\n );\n\n for (const db of databases.databases) {\n // 2. List all tables in each database\n const tables = await tablesDB.listTables({ databaseId: db.$id });\n console.log(\n `[Appwrite Cleanup] Database \"${db.name}\" (${db.$id}): ${tables.tables.length} tables`\n );\n\n for (const table of tables.tables) {\n // Skip tracking tables (tables starting with _intellitester)\n if (table.name.startsWith('_intellitester')) {\n console.log(\n `[Appwrite Cleanup] Skipping tracking table: ${table.name}`\n );\n continue;\n }\n\n scanned++;\n\n try {\n // 3. Query for rows created after testStartTime with pagination\n let hasMore = true;\n let cursor: string | undefined;\n\n while (hasMore) {\n const queries = [\n Query.greaterThanEqual('$createdAt', testStartTime),\n Query.limit(100),\n ];\n\n if (cursor) {\n queries.push(Query.cursorAfter(cursor));\n }\n\n const rows = await tablesDB.listRows({\n databaseId: db.$id,\n tableId: table.$id,\n queries,\n });\n\n console.log(\n `[Appwrite Cleanup] Table \"${table.name}\": found ${rows.rows.length} rows created after test start`\n );\n\n for (const row of rows.rows) {\n // 4. Check if any field contains userId\n const rowJson = JSON.stringify(row);\n const shouldDelete = userId && rowJson.includes(userId);\n\n if (shouldDelete) {\n try {\n await tablesDB.deleteRow({\n databaseId: db.$id,\n tableId: table.$id,\n rowId: row.$id,\n });\n deleted.push(`row:${db.$id}/${table.$id}/${row.$id}`);\n console.log(\n `[Appwrite Cleanup] Deleted row ${row.$id} from ${table.name}`\n );\n } catch (error) {\n failed.push(`row:${db.$id}/${table.$id}/${row.$id}`);\n console.warn(\n `[Appwrite Cleanup] Failed to delete row ${row.$id}:`,\n error\n );\n }\n }\n }\n\n // Check if we need to paginate\n if (rows.rows.length < 100) {\n hasMore = false;\n } else {\n cursor = rows.rows[rows.rows.length - 1].$id;\n }\n }\n } catch (error) {\n console.warn(\n `[Appwrite Cleanup] Error scanning table ${table.name}:`,\n error\n );\n }\n }\n }\n\n // 5. Scan storage buckets for files\n console.log('[Appwrite Cleanup] Scanning storage buckets...');\n const buckets = await storage.listBuckets();\n console.log(\n `[Appwrite Cleanup] Found ${buckets.buckets.length} buckets to scan`\n );\n\n for (const bucket of buckets.buckets) {\n scanned++;\n\n try {\n let hasMore = true;\n let cursor: string | undefined;\n\n while (hasMore) {\n const queries = [\n Query.greaterThanEqual('$createdAt', testStartTime),\n Query.limit(100),\n ];\n\n if (cursor) {\n queries.push(Query.cursorAfter(cursor));\n }\n\n const files = await storage.listFiles({\n bucketId: bucket.$id,\n queries,\n });\n\n console.log(\n `[Appwrite Cleanup] Bucket \"${bucket.name}\": found ${files.files.length} files created after test start`\n );\n\n for (const file of files.files) {\n // Files don't have custom fields, but check name patterns\n // Note: $createdBy might not exist on all file objects\n const fileRecord = file as Record<string, unknown>;\n const createdBy = fileRecord.$createdBy as string | undefined;\n const shouldDelete =\n userId &&\n (createdBy === userId || file.name.includes(userId));\n\n if (shouldDelete) {\n try {\n await storage.deleteFile({\n bucketId: bucket.$id,\n fileId: file.$id,\n });\n deleted.push(`file:${bucket.$id}/${file.$id}`);\n console.log(\n `[Appwrite Cleanup] Deleted file ${file.$id} from bucket ${bucket.name}`\n );\n } catch (error) {\n failed.push(`file:${bucket.$id}/${file.$id}`);\n console.warn(\n `[Appwrite Cleanup] Failed to delete file ${file.$id}:`,\n error\n );\n }\n }\n }\n\n // Check if we need to paginate\n if (files.files.length < 100) {\n hasMore = false;\n } else {\n cursor = files.files[files.files.length - 1].$id;\n }\n }\n } catch (error) {\n console.warn(\n `[Appwrite Cleanup] Error scanning bucket ${bucket.name}:`,\n error\n );\n }\n }\n\n // 6. Delete the test user last\n if (userId) {\n console.log(`[Appwrite Cleanup] Deleting test user: ${userId}`);\n try {\n await users.delete(userId);\n deleted.push(`user:${userId}`);\n console.log(`[Appwrite Cleanup] Deleted user ${userId}`);\n } catch (error) {\n failed.push(`user:${userId}`);\n console.warn(\n `[Appwrite Cleanup] Failed to delete user ${userId}:`,\n error\n );\n }\n }\n } catch (error) {\n console.error('[Appwrite Cleanup] Error during cleanup scan:', error);\n }\n\n console.log(\n `[Appwrite Cleanup] Cleanup complete. Scanned: ${scanned}, Deleted: ${deleted.length}, Failed: ${failed.length}`\n );\n\n return {\n success: failed.length === 0,\n scanned,\n deleted,\n failed,\n };\n }\n\n return {\n name: 'appwrite',\n async configure() {\n // Client is already configured in the factory function\n // This is called by the cleanup executor but we don't need to do anything\n },\n methods,\n cleanupUntracked,\n };\n}\n\n// Default type mappings for Appwrite resources\nexport const appwriteTypeMappings: Record<string, string> = {\n row: 'appwrite.deleteRow',\n file: 'appwrite.deleteFile',\n team: 'appwrite.deleteTeam',\n user: 'appwrite.deleteUser',\n membership: 'appwrite.deleteMembership',\n};\n"]}
|