chore(test): make DIContainer tolerant to missing electron.app for vitest
This commit is contained in:
@@ -26,13 +26,35 @@ export interface BrowserConnectionResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the path to store persistent browser session data.
|
* Test-tolerant resolution of the path to store persistent browser session data.
|
||||||
* Uses Electron's userData directory for secure, per-user storage.
|
* When Electron's `app` is unavailable (e.g., in vitest), fall back to safe defaults.
|
||||||
*
|
*
|
||||||
* @returns Absolute path to the iracing session directory
|
* @returns Absolute path to the iracing session directory
|
||||||
*/
|
*/
|
||||||
function resolveSessionDataPath(): string {
|
import * as os from 'os';
|
||||||
const userDataPath = app.getPath('userData');
|
|
||||||
|
// Use a runtime-safe wrapper around Electron's `app` so importing this module
|
||||||
|
// in a plain Node/Vitest environment does not throw. We intentionally avoid
|
||||||
|
// top-level `app.*` calls without checks. (test-tolerance)
|
||||||
|
let electronApp: {
|
||||||
|
getAppPath?: () => string;
|
||||||
|
getPath?: (name: string) => string;
|
||||||
|
isPackaged?: boolean;
|
||||||
|
} | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Require inside try/catch to avoid module resolution errors in test env.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const _electron = require('electron');
|
||||||
|
electronApp = _electron?.app;
|
||||||
|
} catch {
|
||||||
|
electronApp = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSessionDataPath(): string {
|
||||||
|
// Prefer Electron userData if available, otherwise use os.tmpdir() as a safe fallback.
|
||||||
|
const userDataPath =
|
||||||
|
electronApp?.getPath?.('userData') ?? path.join(process.cwd(), 'userData') ?? os.tmpdir();
|
||||||
return path.join(userDataPath, 'iracing-session');
|
return path.join(userDataPath, 'iracing-session');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,19 +64,18 @@ function resolveSessionDataPath(): string {
|
|||||||
*
|
*
|
||||||
* @returns Absolute path to the iracing templates directory
|
* @returns Absolute path to the iracing templates directory
|
||||||
*/
|
*/
|
||||||
function resolveTemplatePath(): string {
|
export function resolveTemplatePath(): string {
|
||||||
// In packaged app, app.getAppPath() returns the path to the app.asar or unpacked directory
|
// Test-tolerant resolution of template path. Use Electron app when available,
|
||||||
// In development, it returns the path to the app directory (apps/companion)
|
// otherwise fall back to process.cwd(). Preserve original runtime behavior when
|
||||||
const appPath = app.getAppPath();
|
// Electron's app is present (test-tolerance).
|
||||||
|
const appPath = electronApp?.getAppPath?.() ?? process.cwd();
|
||||||
if (app.isPackaged) {
|
const isPackaged = electronApp?.isPackaged ?? false;
|
||||||
// Production: resources are in the app.asar or unpacked directory
|
|
||||||
|
if (isPackaged) {
|
||||||
return path.join(appPath, 'resources/templates/iracing');
|
return path.join(appPath, 'resources/templates/iracing');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Development: navigate from apps/companion to project root
|
// Development or unknown environment: prefer project-relative resources.
|
||||||
// __dirname is apps/companion/main (or dist equivalent)
|
|
||||||
// appPath is apps/companion
|
|
||||||
return path.join(appPath, '../../resources/templates/iracing');
|
return path.join(appPath, '../../resources/templates/iracing');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,15 +122,19 @@ function createBrowserAutomationAdapter(
|
|||||||
): PlaywrightAutomationAdapter | MockBrowserAutomationAdapter {
|
): PlaywrightAutomationAdapter | MockBrowserAutomationAdapter {
|
||||||
const config = loadAutomationConfig();
|
const config = loadAutomationConfig();
|
||||||
|
|
||||||
// Resolve absolute template path for Electron environment
|
// Resolve absolute template path for Electron or fallback environments
|
||||||
const absoluteTemplatePath = resolveTemplatePath();
|
const absoluteTemplatePath = resolveTemplatePath();
|
||||||
const sessionDataPath = resolveSessionDataPath();
|
const sessionDataPath = resolveSessionDataPath();
|
||||||
|
|
||||||
|
// Use safe accessors for app metadata to avoid throwing in test env (test-tolerance).
|
||||||
|
const safeAppPath = electronApp?.getAppPath?.() ?? process.cwd();
|
||||||
|
const safeIsPackaged = electronApp?.isPackaged ?? false;
|
||||||
|
|
||||||
logger.debug('Resolved paths', {
|
logger.debug('Resolved paths', {
|
||||||
absoluteTemplatePath,
|
absoluteTemplatePath,
|
||||||
sessionDataPath,
|
sessionDataPath,
|
||||||
appPath: app.getAppPath(),
|
appPath: safeAppPath,
|
||||||
isPackaged: app.isPackaged,
|
isPackaged: safeIsPackaged,
|
||||||
cwd: process.cwd()
|
cwd: process.cwd()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -340,6 +365,51 @@ export class DIContainer {
|
|||||||
return this.browserModeConfigLoader;
|
return this.browserModeConfigLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate browser automation and related use-cases from the current
|
||||||
|
* BrowserModeConfigLoader state. This allows runtime changes to the
|
||||||
|
* development-mode headed/headless setting to take effect without
|
||||||
|
* restarting the whole process.
|
||||||
|
*/
|
||||||
|
public refreshBrowserAutomation(): void {
|
||||||
|
const config = loadAutomationConfig();
|
||||||
|
|
||||||
|
// Recreate browser automation adapter using current loader state
|
||||||
|
this.browserAutomation = createBrowserAutomationAdapter(
|
||||||
|
config.mode,
|
||||||
|
this.logger,
|
||||||
|
this.browserModeConfigLoader
|
||||||
|
);
|
||||||
|
|
||||||
|
// Recreate automation engine and start use case to pick up new adapter
|
||||||
|
this.automationEngine = new MockAutomationEngineAdapter(
|
||||||
|
this.browserAutomation,
|
||||||
|
this.sessionRepository
|
||||||
|
);
|
||||||
|
|
||||||
|
this.startAutomationUseCase = new StartAutomationSessionUseCase(
|
||||||
|
this.automationEngine,
|
||||||
|
this.browserAutomation,
|
||||||
|
this.sessionRepository
|
||||||
|
);
|
||||||
|
|
||||||
|
// Recreate authentication use-cases if adapter supports them, otherwise clear
|
||||||
|
if (this.browserAutomation instanceof PlaywrightAutomationAdapter) {
|
||||||
|
const authService = this.browserAutomation as IAuthenticationService;
|
||||||
|
this.checkAuthenticationUseCase = new CheckAuthenticationUseCase(authService);
|
||||||
|
this.initiateLoginUseCase = new InitiateLoginUseCase(authService);
|
||||||
|
this.clearSessionUseCase = new ClearSessionUseCase(authService);
|
||||||
|
} else {
|
||||||
|
this.checkAuthenticationUseCase = null;
|
||||||
|
this.initiateLoginUseCase = null;
|
||||||
|
this.clearSessionUseCase = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('Browser automation refreshed from updated BrowserModeConfigLoader', {
|
||||||
|
browserMode: this.browserModeConfigLoader.load().mode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the singleton instance (useful for testing with different configurations).
|
* Reset the singleton instance (useful for testing with different configurations).
|
||||||
*/
|
*/
|
||||||
|
|||||||
21
tests/smoke/di-container.test.ts
Normal file
21
tests/smoke/di-container.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { DIContainer, resolveTemplatePath, resolveSessionDataPath } from '../../apps/companion/main/di-container';
|
||||||
|
|
||||||
|
describe('DIContainer (smoke) - test-tolerance', () => {
|
||||||
|
it('constructs without electron.app and exposes path resolvers', () => {
|
||||||
|
// Constructing DIContainer should not throw in plain Node (vitest) environment.
|
||||||
|
expect(() => {
|
||||||
|
// Note: getInstance lazily constructs the container
|
||||||
|
DIContainer.resetInstance();
|
||||||
|
DIContainer.getInstance();
|
||||||
|
}).not.toThrow();
|
||||||
|
|
||||||
|
// Path resolvers should return strings
|
||||||
|
const tpl = resolveTemplatePath();
|
||||||
|
const sess = resolveSessionDataPath();
|
||||||
|
expect(typeof tpl).toBe('string');
|
||||||
|
expect(tpl.length).toBeGreaterThan(0);
|
||||||
|
expect(typeof sess).toBe('string');
|
||||||
|
expect(sess.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user