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.
Files changed (89) hide show
  1. package/README.md +200 -0
  2. package/dist/chunk-35WJGNDA.cjs +136 -0
  3. package/dist/chunk-35WJGNDA.cjs.map +1 -0
  4. package/dist/chunk-4B54JUOP.js +234 -0
  5. package/dist/chunk-4B54JUOP.js.map +1 -0
  6. package/dist/chunk-5LFSLMQ7.js +2517 -0
  7. package/dist/chunk-5LFSLMQ7.js.map +1 -0
  8. package/dist/chunk-6PYKWWH5.js +63 -0
  9. package/dist/chunk-6PYKWWH5.js.map +1 -0
  10. package/dist/chunk-ARJYJVRM.cjs +302 -0
  11. package/dist/chunk-ARJYJVRM.cjs.map +1 -0
  12. package/dist/chunk-CN6HSJJX.js +133 -0
  13. package/dist/chunk-CN6HSJJX.js.map +1 -0
  14. package/dist/chunk-DE5UFTTG.js +31 -0
  15. package/dist/chunk-DE5UFTTG.js.map +1 -0
  16. package/dist/chunk-ECBA4GJ3.js +287 -0
  17. package/dist/chunk-ECBA4GJ3.js.map +1 -0
  18. package/dist/chunk-OFXNJXMV.cjs +237 -0
  19. package/dist/chunk-OFXNJXMV.cjs.map +1 -0
  20. package/dist/chunk-PAKODOH4.cjs +66 -0
  21. package/dist/chunk-PAKODOH4.cjs.map +1 -0
  22. package/dist/chunk-QMYM2TCH.cjs +36 -0
  23. package/dist/chunk-QMYM2TCH.cjs.map +1 -0
  24. package/dist/chunk-SAVY6D3X.js +125 -0
  25. package/dist/chunk-SAVY6D3X.js.map +1 -0
  26. package/dist/chunk-UUJXCHVT.cjs +128 -0
  27. package/dist/chunk-UUJXCHVT.cjs.map +1 -0
  28. package/dist/chunk-XWGUA67E.cjs +2552 -0
  29. package/dist/chunk-XWGUA67E.cjs.map +1 -0
  30. package/dist/cli/index.cjs +1985 -0
  31. package/dist/cli/index.cjs.map +1 -0
  32. package/dist/cli/index.d.cts +1 -0
  33. package/dist/cli/index.d.ts +1 -0
  34. package/dist/cli/index.js +1957 -0
  35. package/dist/cli/index.js.map +1 -0
  36. package/dist/core/cleanup/index.cjs +45 -0
  37. package/dist/core/cleanup/index.cjs.map +1 -0
  38. package/dist/core/cleanup/index.d.cts +117 -0
  39. package/dist/core/cleanup/index.d.ts +117 -0
  40. package/dist/core/cleanup/index.js +8 -0
  41. package/dist/core/cleanup/index.js.map +1 -0
  42. package/dist/index.cjs +110 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/dist/index.d.cts +852 -0
  45. package/dist/index.d.ts +852 -0
  46. package/dist/index.js +9 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/integration/index.cjs +22 -0
  49. package/dist/integration/index.cjs.map +1 -0
  50. package/dist/integration/index.d.cts +42 -0
  51. package/dist/integration/index.d.ts +42 -0
  52. package/dist/integration/index.js +20 -0
  53. package/dist/integration/index.js.map +1 -0
  54. package/dist/providers/appwrite/index.cjs +16 -0
  55. package/dist/providers/appwrite/index.cjs.map +1 -0
  56. package/dist/providers/appwrite/index.d.cts +12 -0
  57. package/dist/providers/appwrite/index.d.ts +12 -0
  58. package/dist/providers/appwrite/index.js +3 -0
  59. package/dist/providers/appwrite/index.js.map +1 -0
  60. package/dist/providers/index.cjs +60 -0
  61. package/dist/providers/index.cjs.map +1 -0
  62. package/dist/providers/index.d.cts +13 -0
  63. package/dist/providers/index.d.ts +13 -0
  64. package/dist/providers/index.js +7 -0
  65. package/dist/providers/index.js.map +1 -0
  66. package/dist/providers/mysql/index.cjs +16 -0
  67. package/dist/providers/mysql/index.cjs.map +1 -0
  68. package/dist/providers/mysql/index.d.cts +14 -0
  69. package/dist/providers/mysql/index.d.ts +14 -0
  70. package/dist/providers/mysql/index.js +3 -0
  71. package/dist/providers/mysql/index.js.map +1 -0
  72. package/dist/providers/postgres/index.cjs +16 -0
  73. package/dist/providers/postgres/index.cjs.map +1 -0
  74. package/dist/providers/postgres/index.d.cts +10 -0
  75. package/dist/providers/postgres/index.d.ts +10 -0
  76. package/dist/providers/postgres/index.js +3 -0
  77. package/dist/providers/postgres/index.js.map +1 -0
  78. package/dist/providers/sqlite/index.cjs +16 -0
  79. package/dist/providers/sqlite/index.cjs.map +1 -0
  80. package/dist/providers/sqlite/index.d.cts +11 -0
  81. package/dist/providers/sqlite/index.d.ts +11 -0
  82. package/dist/providers/sqlite/index.js +3 -0
  83. package/dist/providers/sqlite/index.js.map +1 -0
  84. package/dist/types-LONNVTIF.d.cts +56 -0
  85. package/dist/types-l-ZaFKC-.d.ts +56 -0
  86. package/package.json +114 -0
  87. package/schemas/intellitester.config.schema.json +384 -0
  88. package/schemas/test.schema.json +517 -0
  89. package/schemas/workflow.schema.json +227 -0
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # IntelliTester
2
+
3
+ Uses AI + iOS or Android emulators / web browsers to create automated test suites using simple instructions
4
+
5
+ ## Prerequisites
6
+
7
+ Before running web tests with IntelliTester, you need to install Playwright browsers:
8
+
9
+ ```bash
10
+ npx playwright install chromium
11
+ # Or for all browsers:
12
+ npx playwright install
13
+ ```
14
+
15
+ ## Editor Configuration
16
+
17
+ IntelliTester provides JSON schemas for YAML configuration files to enable autocomplete and validation in VS Code and other editors.
18
+
19
+ ### VS Code Setup
20
+
21
+ Add this to your `.vscode/settings.json` (or workspace settings):
22
+
23
+ ```json
24
+ {
25
+ "yaml.schemas": {
26
+ "./node_modules/intellitester/schemas/intellitester.config.schema.json": "intellitester.config.yaml",
27
+ "./node_modules/intellitester/schemas/test.schema.json": "*.test.yaml",
28
+ "./node_modules/intellitester/schemas/workflow.schema.json": "*.workflow.yaml"
29
+ }
30
+ }
31
+ ```
32
+
33
+ This enables:
34
+ - Auto-completion for all configuration properties
35
+ - Inline documentation and examples
36
+ - Real-time validation and error checking
37
+ - Type checking for action steps and configurations
38
+
39
+ ### Using Local Development Schemas
40
+
41
+ If you're working on IntelliTester itself or want to use local schemas, use these paths instead:
42
+
43
+ ```json
44
+ {
45
+ "yaml.schemas": {
46
+ "./schemas/intellitester.config.schema.json": "intellitester.config.yaml",
47
+ "./schemas/test.schema.json": "*.test.yaml",
48
+ "./schemas/workflow.schema.json": "*.workflow.yaml"
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Debugging
54
+
55
+ IntelliTester provides powerful debugging capabilities to help troubleshoot failing tests.
56
+
57
+ ### Debug Mode
58
+
59
+ Run tests with the `--debug` flag to pause execution on failure and open the Playwright Inspector:
60
+
61
+ ```bash
62
+ # Run with debug mode - pauses on failure
63
+ intellitester run tests/login.test.yaml --headed --debug
64
+ ```
65
+
66
+ ### Debug Action Type
67
+
68
+ Add breakpoints directly in your test files using the `debug` action type. This pauses execution and opens the Playwright Inspector at that specific step:
69
+
70
+ ```yaml
71
+ # Add breakpoints in your test
72
+ steps:
73
+ - type: navigate
74
+ value: /signup
75
+ - type: debug # Pauses here, opens Playwright Inspector
76
+ - type: input
77
+ target: { testId: email }
78
+ value: test@example.com
79
+ ```
80
+
81
+ When execution pauses, you can:
82
+ - Inspect the current page state
83
+ - Step through actions manually
84
+ - Examine selectors and elements
85
+ - Continue execution when ready
86
+
87
+ ## Resource Cleanup
88
+
89
+ IntelliTester automatically tracks resources created during tests and cleans them up after execution. This ensures tests don't leave behind database rows, files, users, or other artifacts.
90
+
91
+ ### Built-in Providers
92
+
93
+ IntelliTester includes cleanup providers for common backends:
94
+
95
+ - **Appwrite** - Delete rows, files, teams, users, memberships
96
+ - **PostgreSQL** - Delete rows and users
97
+ - **MySQL** - Delete rows and users
98
+ - **SQLite** - Delete rows and users
99
+
100
+ ### Configuration
101
+
102
+ Configure cleanup in your test YAML files or global config:
103
+
104
+ ```yaml
105
+ # Using Appwrite (backwards compatible)
106
+ appwrite:
107
+ endpoint: https://cloud.appwrite.io/v1
108
+ projectId: ${APPWRITE_PROJECT_ID}
109
+ apiKey: ${APPWRITE_API_KEY}
110
+ cleanup: true
111
+ cleanupOnFailure: true
112
+
113
+ # Or using the new cleanup config
114
+ cleanup:
115
+ provider: appwrite
116
+ parallel: false # Sequential cleanup by default
117
+ retries: 3 # Retry failed deletions
118
+
119
+ # Provider-specific configuration
120
+ appwrite:
121
+ endpoint: ${APPWRITE_ENDPOINT}
122
+ projectId: ${APPWRITE_PROJECT_ID}
123
+ apiKey: ${APPWRITE_API_KEY}
124
+
125
+ # Map resource types to cleanup methods
126
+ types:
127
+ row: appwrite.deleteRow
128
+ team: appwrite.deleteTeam
129
+ stripe_customer: stripe.deleteCustomer
130
+
131
+ # Explicit handler files to load
132
+ handlers:
133
+ - ./src/cleanup/stripe.ts
134
+
135
+ # Auto-discover handlers (enabled by default)
136
+ discover:
137
+ enabled: true
138
+ paths:
139
+ - ./tests/cleanup
140
+ pattern: "**/*.ts"
141
+ ```
142
+
143
+ ### Custom Cleanup Handlers
144
+
145
+ Create custom handlers for resources not covered by built-in providers:
146
+
147
+ ```typescript
148
+ // intellitester.cleanup.ts (auto-discovered at project root)
149
+ import { defineCleanupHandlers } from 'intellitester/cleanup';
150
+ import Stripe from 'stripe';
151
+
152
+ export default defineCleanupHandlers({
153
+ stripe_customer: async (resource) => {
154
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
155
+ await stripe.customers.del(resource.id);
156
+ },
157
+
158
+ stripe_subscription: async (resource) => {
159
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
160
+ await stripe.subscriptions.cancel(resource.id);
161
+ },
162
+ });
163
+ ```
164
+
165
+ Handlers are loaded in this order (later definitions override earlier ones):
166
+
167
+ 1. Built-in provider methods (e.g., `appwrite.deleteRow`)
168
+ 2. `intellitester.cleanup.ts` at project root
169
+ 3. Files in discovery paths (default: `tests/cleanup/**/*.ts`)
170
+ 4. Explicit handler files from config
171
+
172
+ ### Tracking Resources
173
+
174
+ In your app's server-side code, track resources for cleanup:
175
+
176
+ ```typescript
177
+ import { track } from 'intellitester/integration';
178
+
179
+ // Track a database row
180
+ await track({
181
+ type: 'row',
182
+ id: row.$id,
183
+ databaseId: 'main',
184
+ tableId: 'users',
185
+ });
186
+
187
+ // Track a team
188
+ await track({
189
+ type: 'team',
190
+ id: team.$id,
191
+ });
192
+
193
+ // Track a custom resource (requires custom handler)
194
+ await track({
195
+ type: 'stripe_customer',
196
+ id: customer.id,
197
+ });
198
+ ```
199
+
200
+ The `track()` function is production-safe - it's a no-op if the required environment variables aren't set. IntelliTester sets these automatically during test execution.
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ // src/providers/mysql/index.ts
4
+ function createMysqlProvider(config) {
5
+ let connection = null;
6
+ const methods = {
7
+ deleteRow: async (resource) => {
8
+ if (!connection) {
9
+ throw new Error("MySQL connection not initialized. Call configure() first.");
10
+ }
11
+ const table = resource.table;
12
+ const database = resource.database || config.database;
13
+ if (!table) {
14
+ throw new Error(`Missing table name for row ${resource.id}`);
15
+ }
16
+ await connection.execute(
17
+ `DELETE FROM \`${database}\`.\`${table}\` WHERE id = ?`,
18
+ [resource.id]
19
+ );
20
+ },
21
+ deleteUser: async (resource) => {
22
+ if (!connection) {
23
+ throw new Error("MySQL connection not initialized. Call configure() first.");
24
+ }
25
+ const table = resource.table || "users";
26
+ const database = resource.database || config.database;
27
+ await connection.execute(
28
+ `DELETE FROM \`${database}\`.\`${table}\` WHERE id = ?`,
29
+ [resource.id]
30
+ );
31
+ },
32
+ customDelete: async (resource) => {
33
+ if (!connection) {
34
+ throw new Error("MySQL connection not initialized. Call configure() first.");
35
+ }
36
+ const query = resource.query;
37
+ const params = resource.params || [resource.id];
38
+ if (!query) {
39
+ throw new Error(`Missing query for custom delete of resource ${resource.id}`);
40
+ }
41
+ await connection.execute(query, params);
42
+ }
43
+ };
44
+ async function cleanupUntracked(options) {
45
+ if (!connection) {
46
+ throw new Error("MySQL connection not initialized. Call configure() first.");
47
+ }
48
+ const { testStartTime, userId } = options;
49
+ const deleted = [];
50
+ const failed = [];
51
+ let scanned = 0;
52
+ const [tablesRows] = await connection.execute(`
53
+ SELECT TABLE_NAME as table_name
54
+ FROM INFORMATION_SCHEMA.TABLES
55
+ WHERE TABLE_SCHEMA = ?
56
+ AND TABLE_TYPE = 'BASE TABLE'
57
+ AND TABLE_NAME NOT LIKE '_intellitester%'
58
+ `, [config.database]);
59
+ for (const row of tablesRows) {
60
+ const tableName = row.table_name;
61
+ scanned++;
62
+ const [columnsRows] = await connection.execute(`
63
+ SELECT COLUMN_NAME as column_name
64
+ FROM INFORMATION_SCHEMA.COLUMNS
65
+ WHERE TABLE_SCHEMA = ?
66
+ AND TABLE_NAME = ?
67
+ `, [config.database, tableName]);
68
+ const columns = columnsRows.map((r) => r.column_name);
69
+ const hasCreatedAt = columns.some((c) => ["created_at", "createdat", "created"].includes(c.toLowerCase()));
70
+ const userIdColumn = columns.find((c) => ["user_id", "userid", "owner_id", "author_id"].includes(c.toLowerCase()));
71
+ if (!hasCreatedAt) continue;
72
+ const createdAtCol = columns.find((c) => ["created_at", "createdat", "created"].includes(c.toLowerCase()));
73
+ if (!createdAtCol) continue;
74
+ let selectQuery = `SELECT id FROM \`${tableName}\` WHERE `;
75
+ const conditions = [];
76
+ const params = [];
77
+ conditions.push(`\`${createdAtCol}\` >= ?`);
78
+ params.push(testStartTime);
79
+ if (userId && userIdColumn) {
80
+ conditions.push(`\`${userIdColumn}\` = ?`);
81
+ params.push(userId);
82
+ }
83
+ selectQuery += conditions.join(" AND ");
84
+ try {
85
+ const [rowsToDelete] = await connection.execute(selectQuery, params);
86
+ const idsToDelete = rowsToDelete.map((r) => r.id);
87
+ if (idsToDelete.length === 0) continue;
88
+ const deleteQuery = `DELETE FROM \`${tableName}\` WHERE ` + conditions.join(" AND ");
89
+ await connection.execute(deleteQuery, params);
90
+ for (const id of idsToDelete) {
91
+ deleted.push(`${tableName}:${id}`);
92
+ }
93
+ } catch {
94
+ failed.push(`${tableName}:error`);
95
+ }
96
+ }
97
+ return {
98
+ success: failed.length === 0,
99
+ scanned,
100
+ deleted,
101
+ failed
102
+ };
103
+ }
104
+ return {
105
+ name: "mysql",
106
+ async configure() {
107
+ try {
108
+ const mysql = await import('mysql2/promise');
109
+ const createConnection = mysql.createConnection || mysql.default?.createConnection;
110
+ connection = await createConnection({
111
+ host: config.host,
112
+ port: config.port || 3306,
113
+ user: config.user,
114
+ password: config.password,
115
+ database: config.database
116
+ });
117
+ } catch {
118
+ throw new Error(
119
+ 'Failed to initialize MySQL connection. Make sure the "mysql2" package is installed: npm install mysql2'
120
+ );
121
+ }
122
+ },
123
+ methods,
124
+ cleanupUntracked
125
+ };
126
+ }
127
+ var mysqlTypeMappings = {
128
+ row: "mysql.deleteRow",
129
+ user: "mysql.deleteUser",
130
+ custom: "mysql.customDelete"
131
+ };
132
+
133
+ exports.createMysqlProvider = createMysqlProvider;
134
+ exports.mysqlTypeMappings = mysqlTypeMappings;
135
+ //# sourceMappingURL=chunk-35WJGNDA.cjs.map
136
+ //# sourceMappingURL=chunk-35WJGNDA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/mysql/index.ts"],"names":[],"mappings":"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","file":"chunk-35WJGNDA.cjs","sourcesContent":["import type { CleanupProvider, CleanupHandler, CleanupUntrackedOptions, CleanupUntrackedResult } from '../../core/cleanup/types.js';\nimport type { TrackedResource } from '../../integration/index.js';\n\ninterface MysqlConfig {\n host: string;\n port?: number;\n user: string;\n password: string;\n database: string;\n}\n\nexport function createMysqlProvider(config: MysqlConfig): CleanupProvider {\n // Connection will be lazily initialized in configure()\n let connection: any = null;\n\n const methods: Record<string, CleanupHandler> = {\n deleteRow: async (resource: TrackedResource) => {\n if (!connection) {\n throw new Error('MySQL connection not initialized. Call configure() first.');\n }\n\n const table = resource.table as string;\n const database = (resource.database as string) || config.database;\n\n if (!table) {\n throw new Error(`Missing table name for row ${resource.id}`);\n }\n\n // Use parameterized query to prevent SQL injection\n await connection.execute(\n `DELETE FROM \\`${database}\\`.\\`${table}\\` WHERE id = ?`,\n [resource.id]\n );\n },\n\n deleteUser: async (resource: TrackedResource) => {\n if (!connection) {\n throw new Error('MySQL connection not initialized. Call configure() first.');\n }\n\n const table = (resource.table as string) || 'users';\n const database = (resource.database as string) || config.database;\n\n await connection.execute(\n `DELETE FROM \\`${database}\\`.\\`${table}\\` WHERE id = ?`,\n [resource.id]\n );\n },\n\n customDelete: async (resource: TrackedResource) => {\n if (!connection) {\n throw new Error('MySQL connection not initialized. Call configure() first.');\n }\n\n // Allow custom SQL queries via the query property\n const query = resource.query as string;\n const params = (resource.params as any[]) || [resource.id];\n\n if (!query) {\n throw new Error(`Missing query for custom delete of resource ${resource.id}`);\n }\n\n await connection.execute(query, params);\n },\n };\n\n async function cleanupUntracked(options: CleanupUntrackedOptions): Promise<CleanupUntrackedResult> {\n if (!connection) {\n throw new Error('MySQL connection not initialized. Call configure() first.');\n }\n\n const { testStartTime, userId } = options;\n const deleted: string[] = [];\n const failed: string[] = [];\n let scanned = 0;\n\n // 1. Get all tables in the database\n const [tablesRows] = await connection.execute(`\n SELECT TABLE_NAME as table_name\n FROM INFORMATION_SCHEMA.TABLES\n WHERE TABLE_SCHEMA = ?\n AND TABLE_TYPE = 'BASE TABLE'\n AND TABLE_NAME NOT LIKE '_intellitester%'\n `, [config.database]);\n\n for (const row of tablesRows as { table_name: string }[]) {\n const tableName = row.table_name;\n scanned++;\n\n // 2. Check if table has created_at and user_id columns\n const [columnsRows] = await connection.execute(`\n SELECT COLUMN_NAME as column_name\n FROM INFORMATION_SCHEMA.COLUMNS\n WHERE TABLE_SCHEMA = ?\n AND TABLE_NAME = ?\n `, [config.database, tableName]);\n\n const columns: string[] = (columnsRows as { column_name: string }[]).map(r => r.column_name);\n const hasCreatedAt = columns.some(c => ['created_at', 'createdat', 'created'].includes(c.toLowerCase()));\n const userIdColumn = columns.find(c => ['user_id', 'userid', 'owner_id', 'author_id'].includes(c.toLowerCase()));\n\n if (!hasCreatedAt) continue;\n\n // 3. Find the created_at column name\n const createdAtCol = columns.find(c => ['created_at', 'createdat', 'created'].includes(c.toLowerCase()));\n if (!createdAtCol) continue;\n\n // 4. First, select rows to be deleted (MySQL doesn't have RETURNING)\n let selectQuery = `SELECT id FROM \\`${tableName}\\` WHERE `;\n const conditions: string[] = [];\n const params: (string | undefined)[] = [];\n\n // Add created_at condition\n conditions.push(`\\`${createdAtCol}\\` >= ?`);\n params.push(testStartTime);\n\n // Add user_id condition if available\n if (userId && userIdColumn) {\n conditions.push(`\\`${userIdColumn}\\` = ?`);\n params.push(userId);\n }\n\n selectQuery += conditions.join(' AND ');\n\n try {\n // Get IDs of rows to be deleted\n const [rowsToDelete] = await connection.execute(selectQuery, params);\n const idsToDelete = (rowsToDelete as { id: string | number }[]).map(r => r.id);\n\n if (idsToDelete.length === 0) continue;\n\n // Delete the rows\n const deleteQuery = `DELETE FROM \\`${tableName}\\` WHERE ` + conditions.join(' AND ');\n await connection.execute(deleteQuery, params);\n\n // Record deleted IDs\n for (const id of idsToDelete) {\n deleted.push(`${tableName}:${id}`);\n }\n } catch {\n failed.push(`${tableName}:error`);\n }\n }\n\n return {\n success: failed.length === 0,\n scanned,\n deleted,\n failed,\n };\n }\n\n return {\n name: 'mysql',\n async configure() {\n try {\n // Dynamic import since mysql2 is an optional dependency\n // @ts-expect-error - mysql2 is an optional peer dependency\n const mysql = await import('mysql2/promise');\n const createConnection = mysql.createConnection || mysql.default?.createConnection;\n connection = await createConnection({\n host: config.host,\n port: config.port || 3306,\n user: config.user,\n password: config.password,\n database: config.database,\n });\n } catch {\n throw new Error(\n 'Failed to initialize MySQL connection. Make sure the \"mysql2\" package is installed: npm install mysql2'\n );\n }\n },\n methods,\n cleanupUntracked,\n };\n}\n\n// Default type mappings for MySQL resources\nexport const mysqlTypeMappings: Record<string, string> = {\n row: 'mysql.deleteRow',\n user: 'mysql.deleteUser',\n custom: 'mysql.customDelete',\n};\n"]}
@@ -0,0 +1,234 @@
1
+ import { Client, TablesDB, Storage, Teams, Users, Query } from 'node-appwrite';
2
+
3
+ // src/providers/appwrite/index.ts
4
+ function createAppwriteProvider(config) {
5
+ const client = new Client().setEndpoint(config.endpoint).setProject(config.projectId).setKey(config.apiKey);
6
+ const tablesDB = new TablesDB(client);
7
+ const storage = new Storage(client);
8
+ const teams = new Teams(client);
9
+ const users = new Users(client);
10
+ const methods = {
11
+ deleteRow: async (resource) => {
12
+ const databaseId = resource.databaseId;
13
+ const tableId = resource.tableId;
14
+ if (!databaseId || !tableId) {
15
+ throw new Error(`Missing databaseId or tableId for row ${resource.id}`);
16
+ }
17
+ await tablesDB.deleteRow({
18
+ databaseId,
19
+ tableId,
20
+ rowId: resource.id
21
+ });
22
+ },
23
+ deleteFile: async (resource) => {
24
+ const bucketId = resource.bucketId;
25
+ if (!bucketId) {
26
+ throw new Error(`Missing bucketId for file ${resource.id}`);
27
+ }
28
+ await storage.deleteFile(bucketId, resource.id);
29
+ },
30
+ deleteTeam: async (resource) => {
31
+ await teams.delete(resource.id);
32
+ },
33
+ deleteUser: async (resource) => {
34
+ await users.delete(resource.id);
35
+ },
36
+ deleteMembership: async (resource) => {
37
+ const teamId = resource.teamId;
38
+ if (!teamId) {
39
+ throw new Error(`Missing teamId for membership ${resource.id}`);
40
+ }
41
+ await teams.deleteMembership(teamId, resource.id);
42
+ }
43
+ };
44
+ async function cleanupUntracked(options) {
45
+ const { testStartTime, userId, sessionId } = options;
46
+ const deleted = [];
47
+ const failed = [];
48
+ let scanned = 0;
49
+ console.log(
50
+ `[Appwrite Cleanup] Starting untracked cleanup for session ${sessionId || "unknown"}`
51
+ );
52
+ console.log(`[Appwrite Cleanup] Test start time: ${testStartTime}`);
53
+ console.log(`[Appwrite Cleanup] User ID to match: ${userId || "none"}`);
54
+ try {
55
+ const databases = await tablesDB.list();
56
+ console.log(
57
+ `[Appwrite Cleanup] Found ${databases.databases.length} databases to scan`
58
+ );
59
+ for (const db of databases.databases) {
60
+ const tables = await tablesDB.listTables({ databaseId: db.$id });
61
+ console.log(
62
+ `[Appwrite Cleanup] Database "${db.name}" (${db.$id}): ${tables.tables.length} tables`
63
+ );
64
+ for (const table of tables.tables) {
65
+ if (table.name.startsWith("_intellitester")) {
66
+ console.log(
67
+ `[Appwrite Cleanup] Skipping tracking table: ${table.name}`
68
+ );
69
+ continue;
70
+ }
71
+ scanned++;
72
+ try {
73
+ let hasMore = true;
74
+ let cursor;
75
+ while (hasMore) {
76
+ const queries = [
77
+ Query.greaterThanEqual("$createdAt", testStartTime),
78
+ Query.limit(100)
79
+ ];
80
+ if (cursor) {
81
+ queries.push(Query.cursorAfter(cursor));
82
+ }
83
+ const rows = await tablesDB.listRows({
84
+ databaseId: db.$id,
85
+ tableId: table.$id,
86
+ queries
87
+ });
88
+ console.log(
89
+ `[Appwrite Cleanup] Table "${table.name}": found ${rows.rows.length} rows created after test start`
90
+ );
91
+ for (const row of rows.rows) {
92
+ const rowJson = JSON.stringify(row);
93
+ const shouldDelete = userId && rowJson.includes(userId);
94
+ if (shouldDelete) {
95
+ try {
96
+ await tablesDB.deleteRow({
97
+ databaseId: db.$id,
98
+ tableId: table.$id,
99
+ rowId: row.$id
100
+ });
101
+ deleted.push(`row:${db.$id}/${table.$id}/${row.$id}`);
102
+ console.log(
103
+ `[Appwrite Cleanup] Deleted row ${row.$id} from ${table.name}`
104
+ );
105
+ } catch (error) {
106
+ failed.push(`row:${db.$id}/${table.$id}/${row.$id}`);
107
+ console.warn(
108
+ `[Appwrite Cleanup] Failed to delete row ${row.$id}:`,
109
+ error
110
+ );
111
+ }
112
+ }
113
+ }
114
+ if (rows.rows.length < 100) {
115
+ hasMore = false;
116
+ } else {
117
+ cursor = rows.rows[rows.rows.length - 1].$id;
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.warn(
122
+ `[Appwrite Cleanup] Error scanning table ${table.name}:`,
123
+ error
124
+ );
125
+ }
126
+ }
127
+ }
128
+ console.log("[Appwrite Cleanup] Scanning storage buckets...");
129
+ const buckets = await storage.listBuckets();
130
+ console.log(
131
+ `[Appwrite Cleanup] Found ${buckets.buckets.length} buckets to scan`
132
+ );
133
+ for (const bucket of buckets.buckets) {
134
+ scanned++;
135
+ try {
136
+ let hasMore = true;
137
+ let cursor;
138
+ while (hasMore) {
139
+ const queries = [
140
+ Query.greaterThanEqual("$createdAt", testStartTime),
141
+ Query.limit(100)
142
+ ];
143
+ if (cursor) {
144
+ queries.push(Query.cursorAfter(cursor));
145
+ }
146
+ const files = await storage.listFiles({
147
+ bucketId: bucket.$id,
148
+ queries
149
+ });
150
+ console.log(
151
+ `[Appwrite Cleanup] Bucket "${bucket.name}": found ${files.files.length} files created after test start`
152
+ );
153
+ for (const file of files.files) {
154
+ const fileRecord = file;
155
+ const createdBy = fileRecord.$createdBy;
156
+ const shouldDelete = userId && (createdBy === userId || file.name.includes(userId));
157
+ if (shouldDelete) {
158
+ try {
159
+ await storage.deleteFile({
160
+ bucketId: bucket.$id,
161
+ fileId: file.$id
162
+ });
163
+ deleted.push(`file:${bucket.$id}/${file.$id}`);
164
+ console.log(
165
+ `[Appwrite Cleanup] Deleted file ${file.$id} from bucket ${bucket.name}`
166
+ );
167
+ } catch (error) {
168
+ failed.push(`file:${bucket.$id}/${file.$id}`);
169
+ console.warn(
170
+ `[Appwrite Cleanup] Failed to delete file ${file.$id}:`,
171
+ error
172
+ );
173
+ }
174
+ }
175
+ }
176
+ if (files.files.length < 100) {
177
+ hasMore = false;
178
+ } else {
179
+ cursor = files.files[files.files.length - 1].$id;
180
+ }
181
+ }
182
+ } catch (error) {
183
+ console.warn(
184
+ `[Appwrite Cleanup] Error scanning bucket ${bucket.name}:`,
185
+ error
186
+ );
187
+ }
188
+ }
189
+ if (userId) {
190
+ console.log(`[Appwrite Cleanup] Deleting test user: ${userId}`);
191
+ try {
192
+ await users.delete(userId);
193
+ deleted.push(`user:${userId}`);
194
+ console.log(`[Appwrite Cleanup] Deleted user ${userId}`);
195
+ } catch (error) {
196
+ failed.push(`user:${userId}`);
197
+ console.warn(
198
+ `[Appwrite Cleanup] Failed to delete user ${userId}:`,
199
+ error
200
+ );
201
+ }
202
+ }
203
+ } catch (error) {
204
+ console.error("[Appwrite Cleanup] Error during cleanup scan:", error);
205
+ }
206
+ console.log(
207
+ `[Appwrite Cleanup] Cleanup complete. Scanned: ${scanned}, Deleted: ${deleted.length}, Failed: ${failed.length}`
208
+ );
209
+ return {
210
+ success: failed.length === 0,
211
+ scanned,
212
+ deleted,
213
+ failed
214
+ };
215
+ }
216
+ return {
217
+ name: "appwrite",
218
+ async configure() {
219
+ },
220
+ methods,
221
+ cleanupUntracked
222
+ };
223
+ }
224
+ var appwriteTypeMappings = {
225
+ row: "appwrite.deleteRow",
226
+ file: "appwrite.deleteFile",
227
+ team: "appwrite.deleteTeam",
228
+ user: "appwrite.deleteUser",
229
+ membership: "appwrite.deleteMembership"
230
+ };
231
+
232
+ export { appwriteTypeMappings, createAppwriteProvider };
233
+ //# sourceMappingURL=chunk-4B54JUOP.js.map
234
+ //# sourceMappingURL=chunk-4B54JUOP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/appwrite/index.ts"],"names":[],"mappings":"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","file":"chunk-4B54JUOP.js","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"]}