appwrite-utils-cli 1.5.2 → 1.6.0
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/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +478 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +181 -1172
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +278 -1596
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- package/src/utils/schemaStrings.ts +0 -517
@@ -0,0 +1,463 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { jest } from '@jest/globals';
|
4
|
+
import { TestUtils } from '../testUtils';
|
5
|
+
import { AdapterFactory } from '../../src/adapters/AdapterFactory';
|
6
|
+
import { MessageFormatter } from '../../src/shared/messageFormatter';
|
7
|
+
|
8
|
+
// Mock dependencies
|
9
|
+
jest.mock('../../src/shared/messageFormatter');
|
10
|
+
jest.mock('../../src/adapters/AdapterFactory');
|
11
|
+
jest.mock('../../src/utils/versionDetection');
|
12
|
+
|
13
|
+
// Import the actual modules we're testing
|
14
|
+
import { loadConfig } from '../../src/utils/loadConfigs';
|
15
|
+
|
16
|
+
describe('Sync Operations Integration Tests', () => {
|
17
|
+
let testDir: string;
|
18
|
+
let mockAdapter: any;
|
19
|
+
|
20
|
+
beforeEach(() => {
|
21
|
+
jest.clearAllMocks();
|
22
|
+
|
23
|
+
// Create mock adapter with all required methods
|
24
|
+
mockAdapter = {
|
25
|
+
syncFromAppwrite: jest.fn(),
|
26
|
+
syncToAppwrite: jest.fn(),
|
27
|
+
validateConfiguration: jest.fn(),
|
28
|
+
generateYamlConfig: jest.fn(),
|
29
|
+
getDatabases: jest.fn(),
|
30
|
+
getCollections: jest.fn(),
|
31
|
+
createCollection: jest.fn(),
|
32
|
+
updateCollection: jest.fn(),
|
33
|
+
deleteCollection: jest.fn(),
|
34
|
+
client: TestUtils.createMockAppwriteResponses(),
|
35
|
+
};
|
36
|
+
|
37
|
+
(AdapterFactory.createAdapter as jest.Mock).mockResolvedValue(mockAdapter);
|
38
|
+
});
|
39
|
+
|
40
|
+
afterEach(() => {
|
41
|
+
TestUtils.cleanup();
|
42
|
+
});
|
43
|
+
|
44
|
+
describe('Sync from Appwrite to Local Configuration', () => {
|
45
|
+
it('should sync collections to collections/ directory for database API', async () => {
|
46
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
47
|
+
|
48
|
+
// Mock Appwrite response for collections
|
49
|
+
mockAdapter.getDatabases.mockResolvedValue([
|
50
|
+
{
|
51
|
+
$id: 'test-db-id',
|
52
|
+
name: 'test-database',
|
53
|
+
enabled: true,
|
54
|
+
}
|
55
|
+
]);
|
56
|
+
|
57
|
+
mockAdapter.getCollections.mockResolvedValue([
|
58
|
+
{
|
59
|
+
$id: 'synced-collection',
|
60
|
+
name: 'SyncedCollection',
|
61
|
+
documentSecurity: false,
|
62
|
+
enabled: true,
|
63
|
+
$permissions: [],
|
64
|
+
attributes: [
|
65
|
+
{
|
66
|
+
key: 'title',
|
67
|
+
type: 'string',
|
68
|
+
size: 255,
|
69
|
+
required: true,
|
70
|
+
array: false,
|
71
|
+
}
|
72
|
+
],
|
73
|
+
indexes: [],
|
74
|
+
$createdAt: new Date().toISOString(),
|
75
|
+
$updatedAt: new Date().toISOString(),
|
76
|
+
}
|
77
|
+
]);
|
78
|
+
|
79
|
+
mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
|
80
|
+
const collectionsDir = path.join(outputDir, 'collections');
|
81
|
+
fs.mkdirSync(collectionsDir, { recursive: true });
|
82
|
+
|
83
|
+
collections.forEach((collection: any) => {
|
84
|
+
const yamlContent = `
|
85
|
+
name: ${collection.name}
|
86
|
+
id: ${collection.$id}
|
87
|
+
documentSecurity: ${collection.documentSecurity}
|
88
|
+
enabled: ${collection.enabled}
|
89
|
+
permissions: []
|
90
|
+
attributes:
|
91
|
+
- key: ${collection.attributes[0].key}
|
92
|
+
type: ${collection.attributes[0].type}
|
93
|
+
size: ${collection.attributes[0].size}
|
94
|
+
required: ${collection.attributes[0].required}
|
95
|
+
indexes: []
|
96
|
+
`;
|
97
|
+
fs.writeFileSync(
|
98
|
+
path.join(collectionsDir, `${collection.name}.yaml`),
|
99
|
+
yamlContent
|
100
|
+
);
|
101
|
+
});
|
102
|
+
});
|
103
|
+
|
104
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
105
|
+
|
106
|
+
// Verify collections were written to collections/ directory
|
107
|
+
const collectionsDir = path.join(testDir, 'collections');
|
108
|
+
expect(fs.existsSync(collectionsDir)).toBe(true);
|
109
|
+
|
110
|
+
const collectionFiles = fs.readdirSync(collectionsDir);
|
111
|
+
expect(collectionFiles).toContain('SyncedCollection.yaml');
|
112
|
+
|
113
|
+
const collectionContent = fs.readFileSync(
|
114
|
+
path.join(collectionsDir, 'SyncedCollection.yaml'),
|
115
|
+
'utf8'
|
116
|
+
);
|
117
|
+
expect(collectionContent).toContain('name: SyncedCollection');
|
118
|
+
});
|
119
|
+
|
120
|
+
it('should sync tables to tables/ directory for tablesdb API', async () => {
|
121
|
+
testDir = TestUtils.createTestProject({ hasTables: true });
|
122
|
+
|
123
|
+
// Mock TablesDB adapter
|
124
|
+
mockAdapter.getDatabases.mockResolvedValue([
|
125
|
+
{
|
126
|
+
$id: 'test-db-id',
|
127
|
+
name: 'test-database',
|
128
|
+
enabled: true,
|
129
|
+
}
|
130
|
+
]);
|
131
|
+
|
132
|
+
mockAdapter.getCollections.mockResolvedValue([
|
133
|
+
{
|
134
|
+
tableId: 'synced-table',
|
135
|
+
name: 'SyncedTable',
|
136
|
+
databaseId: 'test-db-id',
|
137
|
+
documentSecurity: false,
|
138
|
+
enabled: true,
|
139
|
+
$permissions: [],
|
140
|
+
attributes: [
|
141
|
+
{
|
142
|
+
key: 'content',
|
143
|
+
type: 'string',
|
144
|
+
size: 1000,
|
145
|
+
required: true,
|
146
|
+
array: false,
|
147
|
+
}
|
148
|
+
],
|
149
|
+
indexes: [],
|
150
|
+
$createdAt: new Date().toISOString(),
|
151
|
+
$updatedAt: new Date().toISOString(),
|
152
|
+
}
|
153
|
+
]);
|
154
|
+
|
155
|
+
mockAdapter.generateYamlConfig.mockImplementation((tables, outputDir) => {
|
156
|
+
const tablesDir = path.join(outputDir, 'tables');
|
157
|
+
fs.mkdirSync(tablesDir, { recursive: true });
|
158
|
+
|
159
|
+
tables.forEach((table: any) => {
|
160
|
+
const yamlContent = `
|
161
|
+
name: ${table.name}
|
162
|
+
tableId: ${table.tableId}
|
163
|
+
databaseId: ${table.databaseId}
|
164
|
+
documentSecurity: ${table.documentSecurity}
|
165
|
+
enabled: ${table.enabled}
|
166
|
+
permissions: []
|
167
|
+
attributes:
|
168
|
+
- key: ${table.attributes[0].key}
|
169
|
+
type: ${table.attributes[0].type}
|
170
|
+
size: ${table.attributes[0].size}
|
171
|
+
required: ${table.attributes[0].required}
|
172
|
+
indexes: []
|
173
|
+
`;
|
174
|
+
fs.writeFileSync(
|
175
|
+
path.join(tablesDir, `${table.name}.yaml`),
|
176
|
+
yamlContent
|
177
|
+
);
|
178
|
+
});
|
179
|
+
});
|
180
|
+
|
181
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
182
|
+
|
183
|
+
// Verify tables were written to tables/ directory
|
184
|
+
const tablesDir = path.join(testDir, 'tables');
|
185
|
+
expect(fs.existsSync(tablesDir)).toBe(true);
|
186
|
+
|
187
|
+
const tableFiles = fs.readdirSync(tablesDir);
|
188
|
+
expect(tableFiles).toContain('SyncedTable.yaml');
|
189
|
+
|
190
|
+
const tableContent = fs.readFileSync(
|
191
|
+
path.join(tablesDir, 'SyncedTable.yaml'),
|
192
|
+
'utf8'
|
193
|
+
);
|
194
|
+
expect(tableContent).toContain('name: SyncedTable');
|
195
|
+
expect(tableContent).toContain('tableId: synced-table');
|
196
|
+
expect(tableContent).toContain('databaseId: test-db-id');
|
197
|
+
});
|
198
|
+
|
199
|
+
it('should preserve existing configurations during sync', async () => {
|
200
|
+
testDir = TestUtils.createTestProject({
|
201
|
+
hasCollections: true,
|
202
|
+
hasTables: true,
|
203
|
+
});
|
204
|
+
|
205
|
+
// Add existing configurations
|
206
|
+
const existingCollectionContent = fs.readFileSync(
|
207
|
+
path.join(testDir, 'collections', 'TestCollection.ts'),
|
208
|
+
'utf8'
|
209
|
+
);
|
210
|
+
|
211
|
+
mockAdapter.getDatabases.mockResolvedValue([]);
|
212
|
+
mockAdapter.getCollections.mockResolvedValue([]);
|
213
|
+
mockAdapter.generateYamlConfig.mockImplementation(() => {
|
214
|
+
// Don't overwrite existing files
|
215
|
+
});
|
216
|
+
|
217
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
218
|
+
|
219
|
+
// Verify existing files weren't overwritten
|
220
|
+
const currentContent = fs.readFileSync(
|
221
|
+
path.join(testDir, 'collections', 'TestCollection.ts'),
|
222
|
+
'utf8'
|
223
|
+
);
|
224
|
+
expect(currentContent).toBe(existingCollectionContent);
|
225
|
+
});
|
226
|
+
});
|
227
|
+
|
228
|
+
describe('Database Configuration Sync', () => {
|
229
|
+
it('should update main config.yaml with database information', async () => {
|
230
|
+
testDir = TestUtils.createTestProject({ useYaml: true });
|
231
|
+
|
232
|
+
mockAdapter.getDatabases.mockResolvedValue([
|
233
|
+
{
|
234
|
+
$id: 'db1',
|
235
|
+
name: 'Database One',
|
236
|
+
enabled: true,
|
237
|
+
},
|
238
|
+
{
|
239
|
+
$id: 'db2',
|
240
|
+
name: 'Database Two',
|
241
|
+
enabled: false,
|
242
|
+
}
|
243
|
+
]);
|
244
|
+
|
245
|
+
mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
|
246
|
+
// Update the main config file
|
247
|
+
const configPath = path.join(outputDir, '.appwrite', 'config.yaml');
|
248
|
+
const currentConfig = fs.readFileSync(configPath, 'utf8');
|
249
|
+
const updatedConfig = currentConfig + `
|
250
|
+
# Synced databases
|
251
|
+
syncedDatabases:
|
252
|
+
- $id: db1
|
253
|
+
name: Database One
|
254
|
+
enabled: true
|
255
|
+
- $id: db2
|
256
|
+
name: Database Two
|
257
|
+
enabled: false
|
258
|
+
`;
|
259
|
+
fs.writeFileSync(configPath, updatedConfig);
|
260
|
+
});
|
261
|
+
|
262
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
263
|
+
|
264
|
+
const configContent = fs.readFileSync(
|
265
|
+
path.join(testDir, '.appwrite', 'config.yaml'),
|
266
|
+
'utf8'
|
267
|
+
);
|
268
|
+
expect(configContent).toContain('syncedDatabases:');
|
269
|
+
expect(configContent).toContain('Database One');
|
270
|
+
expect(configContent).toContain('Database Two');
|
271
|
+
});
|
272
|
+
});
|
273
|
+
|
274
|
+
describe('Version-Aware Sync Operations', () => {
|
275
|
+
it('should use appropriate directory based on detected API version', async () => {
|
276
|
+
testDir = TestUtils.createTestProject();
|
277
|
+
|
278
|
+
// Mock version detection for old version (should use collections/)
|
279
|
+
const oldVersionAdapter = { ...mockAdapter };
|
280
|
+
oldVersionAdapter.getCollections.mockResolvedValue([
|
281
|
+
TestUtils.createTestCollection({ name: 'OldVersionCollection' })
|
282
|
+
]);
|
283
|
+
|
284
|
+
await oldVersionAdapter.syncFromAppwrite(testDir);
|
285
|
+
|
286
|
+
// For newer versions, should use tables/
|
287
|
+
const newVersionAdapter = { ...mockAdapter };
|
288
|
+
newVersionAdapter.getCollections.mockResolvedValue([
|
289
|
+
TestUtils.createTestTable({ name: 'NewVersionTable' })
|
290
|
+
]);
|
291
|
+
|
292
|
+
await newVersionAdapter.syncFromAppwrite(testDir);
|
293
|
+
|
294
|
+
// Verify appropriate handling based on version
|
295
|
+
expect(mockAdapter.syncFromAppwrite).toHaveBeenCalledTimes(2);
|
296
|
+
});
|
297
|
+
|
298
|
+
it('should handle mixed API environments gracefully', async () => {
|
299
|
+
testDir = TestUtils.createTestProject({
|
300
|
+
hasCollections: true,
|
301
|
+
hasTables: true,
|
302
|
+
});
|
303
|
+
|
304
|
+
// Mock mixed response
|
305
|
+
mockAdapter.getCollections.mockResolvedValue([
|
306
|
+
TestUtils.createTestCollection({ name: 'MixedCollection' }),
|
307
|
+
TestUtils.createTestTable({ name: 'MixedTable' }),
|
308
|
+
]);
|
309
|
+
|
310
|
+
mockAdapter.generateYamlConfig.mockImplementation((items, outputDir) => {
|
311
|
+
items.forEach((item: any) => {
|
312
|
+
const isTable = item._isFromTablesDir || item.tableId;
|
313
|
+
const dir = isTable ? 'tables' : 'collections';
|
314
|
+
const dirPath = path.join(outputDir, dir);
|
315
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
316
|
+
|
317
|
+
const yamlContent = `name: ${item.name}\n`;
|
318
|
+
fs.writeFileSync(
|
319
|
+
path.join(dirPath, `${item.name}.yaml`),
|
320
|
+
yamlContent
|
321
|
+
);
|
322
|
+
});
|
323
|
+
});
|
324
|
+
|
325
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
326
|
+
|
327
|
+
// Should handle both types appropriately
|
328
|
+
expect(mockAdapter.generateYamlConfig).toHaveBeenCalled();
|
329
|
+
});
|
330
|
+
});
|
331
|
+
|
332
|
+
describe('Error Handling and Recovery', () => {
|
333
|
+
it('should handle network errors during sync gracefully', async () => {
|
334
|
+
testDir = TestUtils.createTestProject();
|
335
|
+
|
336
|
+
mockAdapter.getDatabases.mockRejectedValue(new Error('Network timeout'));
|
337
|
+
|
338
|
+
await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow('Network timeout');
|
339
|
+
|
340
|
+
// Verify that partial configurations are not left in inconsistent state
|
341
|
+
const collectionsDir = path.join(testDir, 'collections');
|
342
|
+
const tablesDir = path.join(testDir, 'tables');
|
343
|
+
|
344
|
+
// Should not create partial directories on failure
|
345
|
+
if (fs.existsSync(collectionsDir)) {
|
346
|
+
const files = fs.readdirSync(collectionsDir);
|
347
|
+
expect(files.length).toBe(0); // No partial files
|
348
|
+
}
|
349
|
+
});
|
350
|
+
|
351
|
+
it('should validate synced configurations before writing', async () => {
|
352
|
+
testDir = TestUtils.createTestProject();
|
353
|
+
|
354
|
+
// Mock invalid configuration from Appwrite
|
355
|
+
mockAdapter.getCollections.mockResolvedValue([
|
356
|
+
{
|
357
|
+
// Missing required fields
|
358
|
+
name: '',
|
359
|
+
$id: '',
|
360
|
+
attributes: [],
|
361
|
+
}
|
362
|
+
]);
|
363
|
+
|
364
|
+
mockAdapter.validateConfiguration.mockReturnValue({
|
365
|
+
isValid: false,
|
366
|
+
errors: ['Collection name is required', 'Collection ID is required'],
|
367
|
+
warnings: [],
|
368
|
+
});
|
369
|
+
|
370
|
+
await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow();
|
371
|
+
|
372
|
+
expect(mockAdapter.validateConfiguration).toHaveBeenCalled();
|
373
|
+
});
|
374
|
+
|
375
|
+
it('should provide rollback capability on sync failure', async () => {
|
376
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
377
|
+
|
378
|
+
// Backup existing state
|
379
|
+
const originalFiles = fs.readdirSync(path.join(testDir, 'collections'));
|
380
|
+
|
381
|
+
mockAdapter.getCollections.mockResolvedValue([
|
382
|
+
TestUtils.createTestCollection({ name: 'NewCollection' })
|
383
|
+
]);
|
384
|
+
|
385
|
+
// Simulate failure during write
|
386
|
+
mockAdapter.generateYamlConfig.mockImplementation(() => {
|
387
|
+
throw new Error('Write permission denied');
|
388
|
+
});
|
389
|
+
|
390
|
+
await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow();
|
391
|
+
|
392
|
+
// Verify original files are preserved
|
393
|
+
const currentFiles = fs.readdirSync(path.join(testDir, 'collections'));
|
394
|
+
expect(currentFiles).toEqual(originalFiles);
|
395
|
+
});
|
396
|
+
});
|
397
|
+
|
398
|
+
describe('Performance Optimization', () => {
|
399
|
+
it('should handle large numbers of collections efficiently', async () => {
|
400
|
+
testDir = TestUtils.createTestProject();
|
401
|
+
|
402
|
+
// Create large number of collections
|
403
|
+
const largeCollectionSet = Array.from({ length: 1000 }, (_, i) =>
|
404
|
+
TestUtils.createTestCollection({
|
405
|
+
name: `Collection${i}`,
|
406
|
+
$id: `collection-${i}`,
|
407
|
+
})
|
408
|
+
);
|
409
|
+
|
410
|
+
mockAdapter.getCollections.mockResolvedValue(largeCollectionSet);
|
411
|
+
mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
|
412
|
+
// Simulate efficient batch writing
|
413
|
+
const collectionsDir = path.join(outputDir, 'collections');
|
414
|
+
fs.mkdirSync(collectionsDir, { recursive: true });
|
415
|
+
|
416
|
+
// Write files in batches to avoid memory issues
|
417
|
+
const batchSize = 100;
|
418
|
+
for (let i = 0; i < collections.length; i += batchSize) {
|
419
|
+
const batch = collections.slice(i, i + batchSize);
|
420
|
+
batch.forEach((collection: any) => {
|
421
|
+
fs.writeFileSync(
|
422
|
+
path.join(collectionsDir, `${collection.name}.yaml`),
|
423
|
+
`name: ${collection.name}\n`
|
424
|
+
);
|
425
|
+
});
|
426
|
+
}
|
427
|
+
});
|
428
|
+
|
429
|
+
const startTime = Date.now();
|
430
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
431
|
+
const syncTime = Date.now() - startTime;
|
432
|
+
|
433
|
+
expect(syncTime).toBeLessThan(10000); // Should complete within 10 seconds
|
434
|
+
|
435
|
+
const collectionFiles = fs.readdirSync(path.join(testDir, 'collections'));
|
436
|
+
expect(collectionFiles.length).toBe(1000);
|
437
|
+
});
|
438
|
+
|
439
|
+
it('should use incremental sync when possible', async () => {
|
440
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
441
|
+
|
442
|
+
// Mock last sync timestamp
|
443
|
+
const lastSyncFile = path.join(testDir, '.appwrite', '.last-sync');
|
444
|
+
fs.writeFileSync(lastSyncFile, new Date().toISOString());
|
445
|
+
|
446
|
+
mockAdapter.getCollections.mockResolvedValue([
|
447
|
+
TestUtils.createTestCollection({
|
448
|
+
name: 'UpdatedCollection',
|
449
|
+
$updatedAt: new Date().toISOString(),
|
450
|
+
})
|
451
|
+
]);
|
452
|
+
|
453
|
+
await mockAdapter.syncFromAppwrite(testDir);
|
454
|
+
|
455
|
+
// Should only sync updated items
|
456
|
+
expect(mockAdapter.getCollections).toHaveBeenCalledWith(
|
457
|
+
expect.objectContaining({
|
458
|
+
modifiedSince: expect.any(String),
|
459
|
+
})
|
460
|
+
);
|
461
|
+
});
|
462
|
+
});
|
463
|
+
});
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/** @type {import('jest').Config} */
|
2
|
+
module.exports = {
|
3
|
+
preset: 'ts-jest',
|
4
|
+
testEnvironment: 'node',
|
5
|
+
roots: ['<rootDir>/src', '<rootDir>/tests'],
|
6
|
+
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
7
|
+
transform: {
|
8
|
+
'^.+\\.ts$': 'ts-jest',
|
9
|
+
},
|
10
|
+
collectCoverageFrom: [
|
11
|
+
'src/**/*.ts',
|
12
|
+
'!src/**/*.d.ts',
|
13
|
+
'!src/main.ts', // Exclude entry point
|
14
|
+
'!src/functions/templates/**', // Exclude template files
|
15
|
+
],
|
16
|
+
coverageDirectory: 'coverage',
|
17
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
18
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
|
19
|
+
testTimeout: 30000,
|
20
|
+
moduleFileExtensions: ['ts', 'js', 'json'],
|
21
|
+
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
22
|
+
clearMocks: true,
|
23
|
+
restoreMocks: true,
|
24
|
+
verbose: true,
|
25
|
+
};
|