From 9053c4778935e0211ab9dc7dc121f6eba5edd804 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Thu, 11 Dec 2025 16:35:52 +0530 Subject: [PATCH] add: tests --- .../src/store/default-workspace.js | 23 +- .../default-workspace.spec.ts | 222 ++++++++++++++++++ .../default-workspace/migration.spec.ts | 204 ++++++++++++++++ 3 files changed, 435 insertions(+), 14 deletions(-) create mode 100644 tests/workspace/default-workspace/default-workspace.spec.ts create mode 100644 tests/workspace/default-workspace/migration.spec.ts diff --git a/packages/bruno-electron/src/store/default-workspace.js b/packages/bruno-electron/src/store/default-workspace.js index ce46fd632..28fd1b998 100644 --- a/packages/bruno-electron/src/store/default-workspace.js +++ b/packages/bruno-electron/src/store/default-workspace.js @@ -78,11 +78,10 @@ class DefaultWorkspaceManager { }; } - // Need to create/recreate default workspace this.initializationPromise = (async () => { try { const shouldMigrate = this.needsMigration(); - const newWorkspacePath = await this.initializeDefaultWorkspace(existingPath, { migrateFromPreferences: shouldMigrate }); + const newWorkspacePath = await this.initializeDefaultWorkspace({ migrateFromPreferences: shouldMigrate }); return { workspacePath: newWorkspacePath, @@ -99,21 +98,17 @@ class DefaultWorkspaceManager { return this.initializationPromise; } - async initializeDefaultWorkspace(existingPath = null, options = {}) { + async initializeDefaultWorkspace(options = {}) { const { migrateFromPreferences = true } = options; - let workspacePath = existingPath; + const configDir = app.getPath('userData'); + const baseWorkspacePath = path.join(configDir, 'default-workspace'); - if (!workspacePath || !fs.existsSync(workspacePath)) { - const configDir = app.getPath('userData'); - const baseWorkspacePath = path.join(configDir, 'default-workspace'); - - workspacePath = baseWorkspacePath; - let counter = 1; - while (fs.existsSync(workspacePath)) { - workspacePath = `${baseWorkspacePath}-${counter}`; - counter++; - } + let workspacePath = baseWorkspacePath; + let counter = 1; + while (fs.existsSync(workspacePath)) { + workspacePath = `${baseWorkspacePath}-${counter}`; + counter++; } fs.mkdirSync(workspacePath, { recursive: true }); diff --git a/tests/workspace/default-workspace/default-workspace.spec.ts b/tests/workspace/default-workspace/default-workspace.spec.ts new file mode 100644 index 000000000..b0cffde21 --- /dev/null +++ b/tests/workspace/default-workspace/default-workspace.spec.ts @@ -0,0 +1,222 @@ +import path from 'path'; +import fs from 'fs'; +import { test, expect } from '../../../playwright'; + +test.describe('Default Workspace', () => { + test.describe('First Launch', () => { + test('should create default workspace with "My Workspace" name on first launch', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-first-launch'); + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + // Verify the workspace name is "My Workspace" in the title bar + const workspaceName = page.locator('.workspace-name'); + await expect(workspaceName).toContainText('My Workspace'); + + await app.close(); + }); + }); + + test.describe('Persistence', () => { + test('should persist default workspace across app restarts', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-persistence'); + + // First launch + const app1 = await launchElectronApp({ userDataPath }); + const page1 = await app1.firstWindow(); + await page1.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + await expect(page1.locator('.workspace-name')).toContainText('My Workspace'); + + await app1.close(); + + // Second launch - same workspace should be loaded + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + await expect(page2.locator('.workspace-name')).toContainText('My Workspace'); + + await app2.close(); + }); + }); + + test.describe('Recovery - Creates NEW workspace (never modifies existing)', () => { + test('should create NEW workspace when existing workspace.yml is deleted', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-recovery-deleted'); + + // Create a corrupted default workspace BEFORE launching app + const defaultWorkspacePath = path.join(userDataPath, 'default-workspace'); + fs.mkdirSync(defaultWorkspacePath, { recursive: true }); + fs.mkdirSync(path.join(defaultWorkspacePath, 'collections'), { recursive: true }); + // Note: NOT creating workspace.yml - simulating deleted file + + // Create preferences pointing to the corrupted workspace + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + general: { + defaultWorkspacePath: defaultWorkspacePath + } + }) + ); + + // Launch app - should create NEW workspace + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + // Should show "My Workspace" + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Old directory should still exist (never deleted) + expect(fs.existsSync(defaultWorkspacePath)).toBe(true); + + // New workspace directory should have been created (default-workspace-1 since default-workspace exists) + const newWorkspacePath = path.join(userDataPath, 'default-workspace-1'); + expect(fs.existsSync(newWorkspacePath)).toBe(true); + expect(fs.existsSync(path.join(newWorkspacePath, 'workspace.yml'))).toBe(true); + + await app.close(); + }); + + test('should create NEW workspace when workspace.yml has invalid YAML', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-recovery-invalid'); + + // Create workspace with invalid YAML BEFORE launching app + const defaultWorkspacePath = path.join(userDataPath, 'default-workspace'); + fs.mkdirSync(defaultWorkspacePath, { recursive: true }); + fs.writeFileSync(path.join(defaultWorkspacePath, 'workspace.yml'), 'invalid: yaml: [[['); + + // Create preferences pointing to the corrupted workspace + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + general: { + defaultWorkspacePath: defaultWorkspacePath + } + }) + ); + + // Launch app - should create NEW workspace + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Old corrupted file should still exist (never deleted) + const oldContent = fs.readFileSync(path.join(defaultWorkspacePath, 'workspace.yml'), 'utf8'); + expect(oldContent).toContain('invalid: yaml: [[['); + + // New workspace should have been created + const newWorkspacePath = path.join(userDataPath, 'default-workspace-1'); + expect(fs.existsSync(newWorkspacePath)).toBe(true); + + await app.close(); + }); + + test('should create NEW workspace when workspace.yml has wrong type', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-recovery-wrong-type'); + + // Create workspace with wrong type BEFORE launching app + const defaultWorkspacePath = path.join(userDataPath, 'default-workspace'); + fs.mkdirSync(defaultWorkspacePath, { recursive: true }); + fs.writeFileSync(path.join(defaultWorkspacePath, 'workspace.yml'), `opencollection: 1.0.0 +info: + name: My Workspace + type: collection +collections: +specs: +docs: '' +`); + + // Create preferences pointing to the invalid workspace + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + general: { + defaultWorkspacePath: defaultWorkspacePath + } + }) + ); + + // Launch app + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // New workspace should have been created + const newWorkspacePath = path.join(userDataPath, 'default-workspace-1'); + expect(fs.existsSync(newWorkspacePath)).toBe(true); + + await app.close(); + }); + + test('should create NEW workspace when directory does not exist', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-recovery-dir-missing'); + + // Create preferences pointing to non-existent directory + const nonExistentPath = path.join(userDataPath, 'non-existent-workspace'); + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + general: { + defaultWorkspacePath: nonExistentPath + } + }) + ); + + // Launch app + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // New workspace should have been created (default-workspace since non-existent doesn't block) + const newWorkspacePath = path.join(userDataPath, 'default-workspace'); + expect(fs.existsSync(newWorkspacePath)).toBe(true); + expect(fs.existsSync(path.join(newWorkspacePath, 'workspace.yml'))).toBe(true); + + await app.close(); + }); + }); + + test.describe('UI Behavior', () => { + test('should display default workspace in workspace dropdown', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-ui-dropdown'); + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + // Click on workspace name to open dropdown + await page.locator('.workspace-name-container').click(); + + // Verify default workspace is shown + const workspaceItem = page.locator('.workspace-item, .dropdown-item').filter({ hasText: 'My Workspace' }); + await expect(workspaceItem.first()).toBeVisible(); + + await app.close(); + }); + + test('should not show pin button for default workspace', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-ui-no-pin'); + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await page.locator('.workspace-name-container').click(); + + const workspaceItem = page.locator('.workspace-item').filter({ hasText: 'My Workspace' }); + // Default workspace should NOT have pin button + await expect(workspaceItem.locator('.pin-btn')).not.toBeVisible(); + + await app.close(); + }); + }); +}); diff --git a/tests/workspace/default-workspace/migration.spec.ts b/tests/workspace/default-workspace/migration.spec.ts new file mode 100644 index 000000000..de5629f6b --- /dev/null +++ b/tests/workspace/default-workspace/migration.spec.ts @@ -0,0 +1,204 @@ +import path from 'path'; +import fs from 'fs'; +import { test, expect } from '../../../playwright'; + +const env = { + DISABLE_SAMPLE_COLLECTION_IMPORT: 'false' +}; + +test.describe('Default Workspace Migration', () => { + test.describe('Migration from lastOpenedCollections', () => { + test('should migrate collections from lastOpenedCollections to new workspace', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-migration'); + + // Create a test collection that would have been opened before + const testCollectionPath = path.join(userDataPath, 'my-old-collection'); + fs.mkdirSync(testCollectionPath, { recursive: true }); + fs.writeFileSync( + path.join(testCollectionPath, 'bruno.json'), + JSON.stringify({ + version: '1', + name: 'My Old Collection', + type: 'collection' + }) + ); + + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + lastOpenedCollections: [testCollectionPath] + }) + ); + + // Launch app - should migrate + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + // Verify default workspace is created with correct name + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Verify workspace was created + const workspacePath = path.join(userDataPath, 'default-workspace'); + expect(fs.existsSync(workspacePath)).toBe(true); + + // Verify workspace.yml exists and contains the migrated collection + const workspaceYmlPath = path.join(workspacePath, 'workspace.yml'); + if (fs.existsSync(workspaceYmlPath)) { + const workspaceYml = fs.readFileSync(workspaceYmlPath, 'utf8'); + expect(workspaceYml).toContain('collections:'); + // Collection should be referenced + expect(workspaceYml).toContain('my-old-collection'); + } + + await app.close(); + }); + + test('should migrate multiple collections from lastOpenedCollections', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-migration-multiple'); + + // Create multiple test collections + const collection1Path = path.join(userDataPath, 'collection-1'); + const collection2Path = path.join(userDataPath, 'collection-2'); + + for (const collPath of [collection1Path, collection2Path]) { + fs.mkdirSync(collPath, { recursive: true }); + fs.writeFileSync( + path.join(collPath, 'bruno.json'), + JSON.stringify({ + version: '1', + name: path.basename(collPath), + type: 'collection' + }) + ); + } + + // Create old-style preferences + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + lastOpenedCollections: [collection1Path, collection2Path] + }) + ); + + // Launch app + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Verify workspace.yml has both collections + const workspacePath = path.join(userDataPath, 'default-workspace'); + const workspaceYmlPath = path.join(workspacePath, 'workspace.yml'); + if (fs.existsSync(workspaceYmlPath)) { + const workspaceYml = fs.readFileSync(workspaceYmlPath, 'utf8'); + expect(workspaceYml).toContain('collection-1'); + expect(workspaceYml).toContain('collection-2'); + } + + await app.close(); + }); + }); + + test.describe('Migration does not affect existing users', () => { + test('should skip sample collection when user has existing collections', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-existing-user'); + + // Create a test collection (simulating existing user) + const oldCollectionPath = path.join(userDataPath, 'old-user-collection'); + fs.mkdirSync(oldCollectionPath, { recursive: true }); + fs.writeFileSync( + path.join(oldCollectionPath, 'bruno.json'), + JSON.stringify({ + version: '1', + name: 'Old User Collection', + type: 'collection' + }) + ); + + // Create old-style preferences with lastOpenedCollections + fs.writeFileSync( + path.join(userDataPath, 'preferences.json'), + JSON.stringify({ + lastOpenedCollections: [oldCollectionPath] + }) + ); + + // Launch app - sample collection should NOT be created (existing user) + const app = await launchElectronApp({ userDataPath, dotEnv: env }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + // Verify default workspace is created + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Sample collection should NOT be created (because user has existing collections) + const sampleCollection = page.locator('#sidebar-collection-name').getByText('Sample API Collection'); + await expect(sampleCollection).not.toBeVisible(); + + await app.close(); + }); + }); + + test.describe('No duplicate workspaces on restart', () => { + test('should reuse existing workspace on subsequent launches', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-reuse'); + + // First launch - creates workspace + const app1 = await launchElectronApp({ userDataPath }); + const page1 = await app1.firstWindow(); + await page1.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + await expect(page1.locator('.workspace-name')).toContainText('My Workspace'); + + // Verify initial workspace was created + const workspacePath = path.join(userDataPath, 'default-workspace'); + expect(fs.existsSync(workspacePath)).toBe(true); + const originalYmlContent = fs.readFileSync(path.join(workspacePath, 'workspace.yml'), 'utf8'); + + await app1.close(); + + // Second launch - should reuse existing workspace + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + await expect(page2.locator('.workspace-name')).toContainText('My Workspace'); + + // workspace.yml should NOT have been modified + const currentYmlContent = fs.readFileSync(path.join(workspacePath, 'workspace.yml'), 'utf8'); + expect(currentYmlContent).toBe(originalYmlContent); + + // No new workspace should have been created + expect(fs.existsSync(path.join(userDataPath, 'default-workspace-1'))).toBe(false); + + await app2.close(); + }); + }); + + test.describe('Clean installation', () => { + test('should create empty workspace on fresh install without old preferences', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('default-workspace-clean'); + + // Launch with completely empty user data (no preferences file) + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await expect(page.locator('.workspace-name')).toContainText('My Workspace'); + + // Verify workspace was created + const workspacePath = path.join(userDataPath, 'default-workspace'); + expect(fs.existsSync(workspacePath)).toBe(true); + + // Verify workspace has empty collections section + const workspaceYmlPath = path.join(workspacePath, 'workspace.yml'); + if (fs.existsSync(workspaceYmlPath)) { + const workspaceYml = fs.readFileSync(workspaceYmlPath, 'utf8'); + // Collections should be empty (just the key) + expect(workspaceYml).toMatch(/collections:\s*\n/); + } + + await app.close(); + }); + }); +});