|
| 1 | +import * as fs from 'fs-extra'; |
| 2 | +import * as path from 'path'; |
| 3 | +import Database from 'better-sqlite3'; |
| 4 | +import type { GlobalSettingsModule, GlobalSettingDefinition } from '../src/global-settings/types'; |
| 5 | + |
| 6 | +// Helper function to dynamically get all defined core setting keys |
| 7 | +async function getDefinedCoreSettingKeys(): Promise<string[]> { |
| 8 | + const definedKeys: string[] = []; |
| 9 | + // Correct path from 'services/backend/tests/' to 'services/backend/src/global-settings/' |
| 10 | + const globalSettingsDir = path.join(__dirname, '..', 'src', 'global-settings'); |
| 11 | + |
| 12 | + try { |
| 13 | + const files = fs.readdirSync(globalSettingsDir); |
| 14 | + |
| 15 | + for (const file of files) { |
| 16 | + // Process only .ts files, excluding index.ts (auto-discovery service) and types.ts |
| 17 | + if (file.endsWith('.ts') && file !== 'index.ts' && file !== 'types.ts') { |
| 18 | + const filePath = path.join(globalSettingsDir, file); |
| 19 | + try { |
| 20 | + const moduleExports = require(filePath); // Dynamically require the .ts file |
| 21 | + |
| 22 | + // Iterate over exports to find the GlobalSettingsModule object |
| 23 | + for (const exportName in moduleExports) { |
| 24 | + const exportedItem = moduleExports[exportName]; |
| 25 | + // Check if it structurally resembles a GlobalSettingsModule |
| 26 | + if ( |
| 27 | + exportedItem && |
| 28 | + typeof exportedItem === 'object' && |
| 29 | + exportedItem.group && // Check for group property |
| 30 | + typeof exportedItem.group === 'object' && |
| 31 | + Array.isArray(exportedItem.settings) // Check for settings array |
| 32 | + ) { |
| 33 | + const settingsModule = exportedItem as GlobalSettingsModule; |
| 34 | + settingsModule.settings.forEach((setting: GlobalSettingDefinition) => { |
| 35 | + definedKeys.push(setting.key); |
| 36 | + }); |
| 37 | + // Assuming one main GlobalSettingsModule export per relevant file |
| 38 | + break; |
| 39 | + } |
| 40 | + } |
| 41 | + } catch (error) { |
| 42 | + console.warn(`Could not load or parse settings from ${file}:`, error); |
| 43 | + // Optionally, rethrow or collect errors if critical |
| 44 | + } |
| 45 | + } |
| 46 | + } |
| 47 | + } catch (error) { |
| 48 | + console.error('Failed to read global-settings directory:', error); |
| 49 | + // Rethrow or handle as a test failure if directory read fails |
| 50 | + throw error; |
| 51 | + } |
| 52 | + |
| 53 | + return [...new Set(definedKeys)]; // Return unique keys |
| 54 | +} |
| 55 | + |
| 56 | +describe('Global Settings Initialization Check', () => { |
| 57 | + const dbPath = path.join(__dirname, '..', 'persistent_data', 'database', 'deploystack.db'); |
| 58 | + let db: Database.Database; |
| 59 | + |
| 60 | + beforeAll(() => { |
| 61 | + try { |
| 62 | + // The database file should exist as '1-setup.e2e.test.ts' must have run and created it. |
| 63 | + // Open in read-only mode as this test only verifies existence. |
| 64 | + db = new Database(dbPath, { readonly: true, fileMustExist: true }); |
| 65 | + } catch (error) { |
| 66 | + console.error( |
| 67 | + `Failed to open database at ${dbPath}. Ensure '1-setup.e2e.test.ts' ran successfully and created the database.`, |
| 68 | + error |
| 69 | + ); |
| 70 | + // Re-throw to fail the test suite early if DB connection fails |
| 71 | + throw error; |
| 72 | + } |
| 73 | + }); |
| 74 | + |
| 75 | + afterAll(() => { |
| 76 | + if (db) { |
| 77 | + db.close(); |
| 78 | + } |
| 79 | + }); |
| 80 | + |
| 81 | + it('should ensure all defined core global settings are created in the database', async () => { |
| 82 | + const definedKeys = await getDefinedCoreSettingKeys(); |
| 83 | + |
| 84 | + expect(definedKeys.length).toBeGreaterThanOrEqual(12); // Expect at least smtp (7) + github-oauth (5) |
| 85 | + |
| 86 | + let dbKeys: string[] = []; |
| 87 | + let settingsFound = false; |
| 88 | + const maxRetries = 10; // Poll for up to 5 seconds (10 * 500ms) |
| 89 | + const retryInterval = 500; // 500 ms |
| 90 | + |
| 91 | + for (let i = 0; i < maxRetries; i++) { |
| 92 | + try { |
| 93 | + const rowsFromDB = db.prepare("SELECT key FROM globalSettings").all() as Array<{ key: string }>; |
| 94 | + dbKeys = rowsFromDB.map(row => row.key); |
| 95 | + |
| 96 | + // Check if all defined keys are present |
| 97 | + let allFound = true; |
| 98 | + // Removed critical log here as the test will fail if settings are not found after polling. |
| 99 | + // if (definedKeys.length > 0 && dbKeys.length === 0 && i === 0) { |
| 100 | + // console.error('[Test 4] Initial Check CRITICAL: Defined settings were found, but the globalSettings table in the DB is empty. This indicates settings were not initialized into the DB during setup. Polling...'); |
| 101 | + // } |
| 102 | + |
| 103 | + for (const definedKey of definedKeys) { |
| 104 | + if (!dbKeys.includes(definedKey)) { |
| 105 | + allFound = false; |
| 106 | + break; |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + if (allFound && definedKeys.length > 0) { // Ensure definedKeys is not empty |
| 111 | + // Additionally, ensure that the number of keys in DB is at least the number of defined keys |
| 112 | + if (dbKeys.length >= definedKeys.length) { |
| 113 | + settingsFound = true; |
| 114 | + // console.log(`[Test 4] All ${definedKeys.length} defined settings found in DB after ${i + 1} attempt(s). DB keys: ${dbKeys.length}`); |
| 115 | + break; |
| 116 | + } |
| 117 | + } |
| 118 | + } catch (error) { |
| 119 | + console.error('[Test 4] Failed to query globalSettings table during poll:', error); |
| 120 | + // If query fails, likely a more significant issue, break and let assertions fail |
| 121 | + break; |
| 122 | + } |
| 123 | + |
| 124 | + if (i < maxRetries - 1) { |
| 125 | + await new Promise(resolve => setTimeout(resolve, retryInterval)); |
| 126 | + // if (i % 4 === 0) { // Log progress periodically |
| 127 | + // console.log(`[Test 4] Retrying DB check for global settings... Attempt ${i + 2}/${maxRetries}`); |
| 128 | + // } |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + // console.log('[Test 4] Final keys found in DB globalSettings table after polling:', dbKeys.length, dbKeys); |
| 133 | + |
| 134 | + if (!settingsFound && definedKeys.length > 0) { |
| 135 | + console.error('[Test 4] FAILURE: After polling, not all defined global settings were found in the database.'); |
| 136 | + } |
| 137 | + |
| 138 | + // Perform final assertions |
| 139 | + for (const definedKey of definedKeys) { |
| 140 | + expect(dbKeys).toContain(definedKey); |
| 141 | + } |
| 142 | + |
| 143 | + // Optional: A stricter check to ensure no unexpected keys are present. |
| 144 | + // This ensures that if settingsFound is true, the dbKeys array actually contains all definedKeys. |
| 145 | + // This assumes that only core settings should be in the DB at this stage of testing |
| 146 | + // and plugins (out of scope for this test) haven't added any. |
| 147 | + // For now, we'll stick to ensuring all defined keys are present. |
| 148 | + // expect(dbKeys.length).toEqual(definedKeys.length); |
| 149 | + // expect(dbKeys.sort()).toEqual(definedKeys.sort()); // Even stricter: exact match |
| 150 | + }); |
| 151 | +}); |
0 commit comments