mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: enhance OpenCode provider tests and UI setup
- Updated unit tests for OpenCode provider to include new authentication indicators. - Refactored ProvidersSetupStep component by removing unnecessary UI elements for better clarity. - Improved board background persistence tests by utilizing a setup function for initializing app state. - Enhanced settings synchronization tests to ensure proper handling of login and app state. These changes improve the testing framework and user interface for OpenCode integration, ensuring a smoother setup and authentication process.
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
|||||||
} from '../../../src/providers/opencode-provider.js';
|
} from '../../../src/providers/opencode-provider.js';
|
||||||
import type { ProviderMessage } from '@automaker/types';
|
import type { ProviderMessage } from '@automaker/types';
|
||||||
import { collectAsyncGenerator } from '../../utils/helpers.js';
|
import { collectAsyncGenerator } from '../../utils/helpers.js';
|
||||||
import { spawnJSONLProcess } from '@automaker/platform';
|
import { spawnJSONLProcess, getOpenCodeAuthIndicators } from '@automaker/platform';
|
||||||
|
|
||||||
vi.mock('@automaker/platform', () => ({
|
vi.mock('@automaker/platform', () => ({
|
||||||
spawnJSONLProcess: vi.fn(),
|
spawnJSONLProcess: vi.fn(),
|
||||||
@@ -13,6 +13,11 @@ vi.mock('@automaker/platform', () => ({
|
|||||||
findCliInWsl: vi.fn().mockReturnValue(null),
|
findCliInWsl: vi.fn().mockReturnValue(null),
|
||||||
createWslCommand: vi.fn(),
|
createWslCommand: vi.fn(),
|
||||||
windowsToWslPath: vi.fn(),
|
windowsToWslPath: vi.fn(),
|
||||||
|
getOpenCodeAuthIndicators: vi.fn().mockResolvedValue({
|
||||||
|
hasAuthFile: false,
|
||||||
|
hasOAuthToken: false,
|
||||||
|
hasApiKey: false,
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('opencode-provider.ts', () => {
|
describe('opencode-provider.ts', () => {
|
||||||
@@ -25,7 +30,8 @@ describe('opencode-provider.ts', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
// Note: Don't use vi.restoreAllMocks() here as it would undo the module-level
|
||||||
|
// mock implementations (like getOpenCodeAuthIndicators) set up with vi.mock()
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -815,6 +821,15 @@ describe('opencode-provider.ts', () => {
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
describe('detectInstallation', () => {
|
describe('detectInstallation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Ensure the mock implementation is set up for each test
|
||||||
|
vi.mocked(getOpenCodeAuthIndicators).mockResolvedValue({
|
||||||
|
hasAuthFile: false,
|
||||||
|
hasOAuthToken: false,
|
||||||
|
hasApiKey: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return installed true when CLI is found', async () => {
|
it('should return installed true when CLI is found', async () => {
|
||||||
(provider as unknown as { cliPath: string }).cliPath = '/usr/local/bin/opencode';
|
(provider as unknown as { cliPath: string }).cliPath = '/usr/local/bin/opencode';
|
||||||
(provider as unknown as { detectedStrategy: string }).detectedStrategy = 'native';
|
(provider as unknown as { detectedStrategy: string }).detectedStrategy = 'native';
|
||||||
|
|||||||
@@ -1271,25 +1271,6 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
|||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-4 py-2 text-sm">
|
|
||||||
{providers.map((provider) => (
|
|
||||||
<div
|
|
||||||
key={provider.id}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center gap-1.5',
|
|
||||||
provider.configured ? 'text-green-500' : 'text-muted-foreground'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{provider.configured ? (
|
|
||||||
<CheckCircle2 className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<div className="w-4 h-4 rounded-full border border-current" />
|
|
||||||
)}
|
|
||||||
<span>{provider.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-between pt-4">
|
<div className="flex justify-between pt-4">
|
||||||
<Button variant="ghost" onClick={onBack} className="text-muted-foreground">
|
<Button variant="ghost" onClick={onBack} className="text-muted-foreground">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
cleanupTempDir,
|
cleanupTempDir,
|
||||||
authenticateForTests,
|
authenticateForTests,
|
||||||
handleLoginScreenIfPresent,
|
handleLoginScreenIfPresent,
|
||||||
|
setupWelcomeView,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
// Create unique temp dirs for this test run
|
// Create unique temp dirs for this test run
|
||||||
@@ -102,53 +103,53 @@ test.describe('Board Background Persistence', () => {
|
|||||||
JSON.stringify({ version: 1 }, null, 2)
|
JSON.stringify({ version: 1 }, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set up app state with both projects in the list (not recent, but in projects list)
|
// Set up welcome view with both projects in the list
|
||||||
await page.addInitScript(
|
await setupWelcomeView(page, {
|
||||||
({ projects }: { projects: string[] }) => {
|
workspaceDir: TEST_TEMP_DIR,
|
||||||
const appState = {
|
recentProjects: [
|
||||||
state: {
|
{
|
||||||
projects: [
|
id: projectAId,
|
||||||
{
|
name: projectAName,
|
||||||
id: projects[0],
|
path: projectAPath,
|
||||||
name: projects[1],
|
lastOpened: new Date(Date.now() - 86400000).toISOString(),
|
||||||
path: projects[2],
|
},
|
||||||
lastOpened: new Date(Date.now() - 86400000).toISOString(),
|
{
|
||||||
theme: 'red',
|
id: projectBId,
|
||||||
},
|
name: projectBName,
|
||||||
{
|
path: projectBPath,
|
||||||
id: projects[3],
|
lastOpened: new Date(Date.now() - 172800000).toISOString(),
|
||||||
name: projects[4],
|
},
|
||||||
path: projects[5],
|
],
|
||||||
lastOpened: new Date(Date.now() - 172800000).toISOString(),
|
});
|
||||||
theme: 'red',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
currentProject: null,
|
|
||||||
currentView: 'welcome',
|
|
||||||
theme: 'red',
|
|
||||||
sidebarOpen: true,
|
|
||||||
apiKeys: { anthropic: '', google: '' },
|
|
||||||
chatSessions: [],
|
|
||||||
chatHistoryOpen: false,
|
|
||||||
maxConcurrency: 3,
|
|
||||||
boardBackgroundByProject: {},
|
|
||||||
},
|
|
||||||
version: 2,
|
|
||||||
};
|
|
||||||
localStorage.setItem('automaker-storage', JSON.stringify(appState));
|
|
||||||
|
|
||||||
// Setup complete
|
await authenticateForTests(page);
|
||||||
const setupState = {
|
|
||||||
state: {
|
// Intercept settings API to use our test projects and clear currentProjectId
|
||||||
setupComplete: true,
|
// This ensures the app shows the welcome view with our test projects
|
||||||
workspaceDir: '/tmp',
|
await page.route('**/api/settings/global', async (route) => {
|
||||||
|
const response = await route.fetch();
|
||||||
|
const json = await response.json();
|
||||||
|
if (json.settings) {
|
||||||
|
// Clear currentProjectId to show welcome view
|
||||||
|
json.settings.currentProjectId = null;
|
||||||
|
// Include our test projects so they appear in the recent projects list
|
||||||
|
json.settings.projects = [
|
||||||
|
{
|
||||||
|
id: projectAId,
|
||||||
|
name: projectAName,
|
||||||
|
path: projectAPath,
|
||||||
|
lastOpened: new Date(Date.now() - 86400000).toISOString(),
|
||||||
},
|
},
|
||||||
version: 0,
|
{
|
||||||
};
|
id: projectBId,
|
||||||
localStorage.setItem('setup-storage', JSON.stringify(setupState));
|
name: projectBName,
|
||||||
},
|
path: projectBPath,
|
||||||
{ projects: [projectAId, projectAName, projectAPath, projectBId, projectBName, projectBPath] }
|
lastOpened: new Date(Date.now() - 172800000).toISOString(),
|
||||||
);
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
await route.fulfill({ response, json });
|
||||||
|
});
|
||||||
|
|
||||||
// Track API calls to /api/settings/project to verify settings are being loaded
|
// Track API calls to /api/settings/project to verify settings are being loaded
|
||||||
const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
|
const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
|
||||||
@@ -163,7 +164,6 @@ test.describe('Board Background Persistence', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Navigate to the app
|
// Navigate to the app
|
||||||
await authenticateForTests(page);
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.waitForLoadState('load');
|
await page.waitForLoadState('load');
|
||||||
await handleLoginScreenIfPresent(page);
|
await handleLoginScreenIfPresent(page);
|
||||||
@@ -179,10 +179,10 @@ test.describe('Board Background Persistence', () => {
|
|||||||
// Wait for board view
|
// Wait for board view
|
||||||
await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 });
|
await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 });
|
||||||
|
|
||||||
// Verify project A is current
|
// Verify project A is current (check header paragraph which is always visible)
|
||||||
await expect(
|
await expect(page.locator('[data-testid="board-view"]').getByText(projectAName)).toBeVisible({
|
||||||
page.locator('[data-testid="project-selector"]').getByText(projectAName)
|
timeout: 5000,
|
||||||
).toBeVisible({ timeout: 5000 });
|
});
|
||||||
|
|
||||||
// CRITICAL: Wait for settings to be loaded (useProjectSettingsLoader hook)
|
// CRITICAL: Wait for settings to be loaded (useProjectSettingsLoader hook)
|
||||||
// This ensures the background settings are fetched from the server
|
// This ensures the background settings are fetched from the server
|
||||||
@@ -196,8 +196,16 @@ test.describe('Board Background Persistence', () => {
|
|||||||
// Wait for initial project load to stabilize
|
// Wait for initial project load to stabilize
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
// Ensure sidebar is expanded before interacting with project selector
|
||||||
|
const expandSidebarButton = page.locator('button:has-text("Expand sidebar")');
|
||||||
|
if (await expandSidebarButton.isVisible()) {
|
||||||
|
await expandSidebarButton.click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
}
|
||||||
|
|
||||||
// Switch to project B (no background)
|
// Switch to project B (no background)
|
||||||
const projectSelector = page.locator('[data-testid="project-selector"]');
|
const projectSelector = page.locator('[data-testid="project-selector"]');
|
||||||
|
await expect(projectSelector).toBeVisible({ timeout: 5000 });
|
||||||
await projectSelector.click();
|
await projectSelector.click();
|
||||||
|
|
||||||
// Wait for dropdown to be visible
|
// Wait for dropdown to be visible
|
||||||
@@ -315,7 +323,6 @@ test.describe('Board Background Persistence', () => {
|
|||||||
name: project[1],
|
name: project[1],
|
||||||
path: project[2],
|
path: project[2],
|
||||||
lastOpened: new Date().toISOString(),
|
lastOpened: new Date().toISOString(),
|
||||||
theme: 'red',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const appState = {
|
const appState = {
|
||||||
@@ -323,8 +330,9 @@ test.describe('Board Background Persistence', () => {
|
|||||||
projects: [projectObj],
|
projects: [projectObj],
|
||||||
currentProject: projectObj,
|
currentProject: projectObj,
|
||||||
currentView: 'board',
|
currentView: 'board',
|
||||||
theme: 'red',
|
theme: 'dark',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
|
skipSandboxWarning: true,
|
||||||
apiKeys: { anthropic: '', google: '' },
|
apiKeys: { anthropic: '', google: '' },
|
||||||
chatSessions: [],
|
chatSessions: [],
|
||||||
chatHistoryOpen: false,
|
chatHistoryOpen: false,
|
||||||
@@ -335,19 +343,44 @@ test.describe('Board Background Persistence', () => {
|
|||||||
};
|
};
|
||||||
localStorage.setItem('automaker-storage', JSON.stringify(appState));
|
localStorage.setItem('automaker-storage', JSON.stringify(appState));
|
||||||
|
|
||||||
// Setup complete
|
// Setup complete - use correct key name
|
||||||
const setupState = {
|
const setupState = {
|
||||||
state: {
|
state: {
|
||||||
|
isFirstRun: false,
|
||||||
setupComplete: true,
|
setupComplete: true,
|
||||||
workspaceDir: '/tmp',
|
skipClaudeSetup: false,
|
||||||
},
|
},
|
||||||
version: 0,
|
version: 1,
|
||||||
};
|
};
|
||||||
localStorage.setItem('setup-storage', JSON.stringify(setupState));
|
localStorage.setItem('automaker-setup', JSON.stringify(setupState));
|
||||||
|
|
||||||
|
// Disable splash screen in tests
|
||||||
|
sessionStorage.setItem('automaker-splash-shown', 'true');
|
||||||
},
|
},
|
||||||
{ project: [projectId, projectName, projectPath] }
|
{ project: [projectId, projectName, projectPath] }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await authenticateForTests(page);
|
||||||
|
|
||||||
|
// Intercept settings API to use our test project instead of the E2E fixture
|
||||||
|
await page.route('**/api/settings/global', async (route) => {
|
||||||
|
const response = await route.fetch();
|
||||||
|
const json = await response.json();
|
||||||
|
// Override to use our test project
|
||||||
|
if (json.settings) {
|
||||||
|
json.settings.currentProjectId = projectId;
|
||||||
|
json.settings.projects = [
|
||||||
|
{
|
||||||
|
id: projectId,
|
||||||
|
name: projectName,
|
||||||
|
path: projectPath,
|
||||||
|
lastOpened: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
await route.fulfill({ response, json });
|
||||||
|
});
|
||||||
|
|
||||||
// Track API calls to /api/settings/project to verify settings are being loaded
|
// Track API calls to /api/settings/project to verify settings are being loaded
|
||||||
const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
|
const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', (request) => {
|
||||||
@@ -360,8 +393,7 @@ test.describe('Board Background Persistence', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Navigate and authenticate
|
// Navigate to the app
|
||||||
await authenticateForTests(page);
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.waitForLoadState('load');
|
await page.waitForLoadState('load');
|
||||||
await handleLoginScreenIfPresent(page);
|
await handleLoginScreenIfPresent(page);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { authenticateForTests } from '../utils';
|
import { authenticateForTests, handleLoginScreenIfPresent } from '../utils';
|
||||||
|
|
||||||
const SETTINGS_PATH = path.resolve(process.cwd(), '../server/data/settings.json');
|
const SETTINGS_PATH = path.resolve(process.cwd(), '../server/data/settings.json');
|
||||||
const WORKSPACE_ROOT = path.resolve(process.cwd(), '../..');
|
const WORKSPACE_ROOT = path.resolve(process.cwd(), '../..');
|
||||||
@@ -109,6 +109,8 @@ test.describe('Settings startup sync race', () => {
|
|||||||
// Ensure authenticated and app is loaded at least to welcome/board.
|
// Ensure authenticated and app is loaded at least to welcome/board.
|
||||||
await authenticateForTests(page);
|
await authenticateForTests(page);
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
await page.waitForLoadState('load');
|
||||||
|
await handleLoginScreenIfPresent(page);
|
||||||
await page
|
await page
|
||||||
.locator('[data-testid="welcome-view"], [data-testid="board-view"]')
|
.locator('[data-testid="welcome-view"], [data-testid="board-view"]')
|
||||||
.first()
|
.first()
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ export interface InstallationStatus {
|
|||||||
*/
|
*/
|
||||||
method?: 'cli' | 'wsl' | 'npm' | 'brew' | 'sdk';
|
method?: 'cli' | 'wsl' | 'npm' | 'brew' | 'sdk';
|
||||||
hasApiKey?: boolean;
|
hasApiKey?: boolean;
|
||||||
|
hasOAuthToken?: boolean;
|
||||||
authenticated?: boolean;
|
authenticated?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user