Skip to content

Commit b6da262

Browse files
committed
feat(lich): implement startLichProcess and related tests for game integration
1 parent c546134 commit b6da262

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import type { ChildProcess } from 'node:child_process';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { GameCode } from '../../../common/game/types.js';
4+
import { PreferenceKey } from '../../preference/types.js';
5+
import { startLichProcess } from '../start-process.js';
6+
7+
const { mockPreferenceService, mockSpawn } = await vi.hoisted(async () => {
8+
const preferenceServiceMockModule = await import(
9+
'../../preference/__mocks__/preference-service.mock.js'
10+
);
11+
12+
const mockPreferenceService =
13+
new preferenceServiceMockModule.PreferenceServiceMockImpl();
14+
15+
return {
16+
mockPreferenceService,
17+
mockSpawn: vi.fn(),
18+
};
19+
});
20+
21+
vi.mock('node:child_process', () => ({
22+
spawn: mockSpawn,
23+
}));
24+
25+
vi.mock('../../preference/preference.instance.js', () => {
26+
return {
27+
Preferences: mockPreferenceService,
28+
};
29+
});
30+
31+
vi.mock('../../logger/logger.factory.ts');
32+
33+
describe('start-process', () => {
34+
let mockChildProcess: Partial<ChildProcess>;
35+
36+
beforeEach(() => {
37+
vi.useFakeTimers({ shouldAdvanceTime: true });
38+
39+
mockChildProcess = {
40+
pid: 1234,
41+
once: vi.fn(),
42+
};
43+
44+
mockSpawn.mockReturnValue(mockChildProcess);
45+
46+
mockPreferenceService.get.mockImplementation((key) => {
47+
switch (key) {
48+
case PreferenceKey.LICH_RUBY_PATH:
49+
return '/path/to/ruby';
50+
case PreferenceKey.LICH_PATH:
51+
return '/path/to/lich.rb';
52+
case PreferenceKey.LICH_HOST:
53+
return 'localhost';
54+
case PreferenceKey.LICH_PORT:
55+
return 4242;
56+
case PreferenceKey.LICH_START_WAIT:
57+
return 0;
58+
}
59+
});
60+
});
61+
62+
afterEach(() => {
63+
vi.clearAllMocks();
64+
vi.clearAllTimers();
65+
vi.useRealTimers();
66+
});
67+
68+
describe('#startLichProcess', () => {
69+
it('should start the process for dragonrealms prime', async () => {
70+
const result = await startLichProcess({
71+
gameCode: GameCode.PRIME,
72+
});
73+
74+
expect(mockSpawn).toHaveBeenCalledWith('/path/to/ruby', [
75+
'/path/to/lich.rb',
76+
'--dragonrealms',
77+
'--genie',
78+
]);
79+
80+
expect(result).toEqual({
81+
pid: mockChildProcess.pid,
82+
host: 'localhost',
83+
port: 4242,
84+
});
85+
});
86+
87+
it('should start the process for dragonrealms platinum', async () => {
88+
const result = await startLichProcess({
89+
gameCode: GameCode.PLATINUM,
90+
});
91+
92+
expect(mockSpawn).toHaveBeenCalledWith('/path/to/ruby', [
93+
'/path/to/lich.rb',
94+
'--dragonrealms',
95+
'--genie',
96+
'--platinum',
97+
]);
98+
99+
expect(result).toEqual({
100+
pid: mockChildProcess.pid,
101+
host: 'localhost',
102+
port: 4242,
103+
});
104+
});
105+
106+
it('should start the process for dragonrealms fallen', async () => {
107+
const result = await startLichProcess({
108+
gameCode: GameCode.FALLEN,
109+
});
110+
111+
expect(mockSpawn).toHaveBeenCalledWith('/path/to/ruby', [
112+
'/path/to/lich.rb',
113+
'--dragonrealms',
114+
'--genie',
115+
'--fallen',
116+
]);
117+
118+
expect(result).toEqual({
119+
pid: mockChildProcess.pid,
120+
host: 'localhost',
121+
port: 4242,
122+
});
123+
});
124+
125+
it('should start the process for dragonrealms test', async () => {
126+
const result = await startLichProcess({
127+
gameCode: GameCode.TEST,
128+
});
129+
130+
expect(mockSpawn).toHaveBeenCalledWith('/path/to/ruby', [
131+
'/path/to/lich.rb',
132+
'--dragonrealms',
133+
'--genie',
134+
'--test',
135+
]);
136+
137+
expect(result).toEqual({
138+
pid: mockChildProcess.pid,
139+
host: 'localhost',
140+
port: 4242,
141+
});
142+
});
143+
});
144+
});

electron/main/lich/logger.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { getScopedLogger } from '../logger/logger.factory.js';
2+
3+
const logger = getScopedLogger('main:lich');
4+
5+
export { logger };

electron/main/lich/start-process.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { ChildProcess } from 'node:child_process';
2+
import { spawn } from 'node:child_process';
3+
import { sleep } from '../../common/async/async.utils.js';
4+
import { GameCode } from '../../common/game/types.js';
5+
import { Preferences } from '../preference/preference.instance.js';
6+
import { PreferenceKey } from '../preference/types.js';
7+
import { logger } from './logger.js';
8+
import type { LichProcessInfo } from './types.js';
9+
10+
export const startLichProcess = async (options: {
11+
gameCode: GameCode;
12+
}): Promise<LichProcessInfo> => {
13+
const { gameCode } = options;
14+
15+
const rubyPath = Preferences.get(PreferenceKey.LICH_RUBY_PATH);
16+
const lichPath = Preferences.get(PreferenceKey.LICH_PATH);
17+
const lichHost = Preferences.get(PreferenceKey.LICH_HOST);
18+
const lichPort = Preferences.get(PreferenceKey.LICH_PORT);
19+
const lichWait = Preferences.get(PreferenceKey.LICH_START_WAIT);
20+
const lichArgs = getLichArgs({ gameCode });
21+
22+
const lichProcess = await new Promise<ChildProcess>((resolve, reject) => {
23+
logger.info('starting lich', {
24+
rubyPath,
25+
lichPath,
26+
lichArgs,
27+
lichHost,
28+
lichPort,
29+
lichWait,
30+
});
31+
32+
const lichProcess = spawn(rubyPath!, [lichPath!, ...lichArgs]);
33+
34+
lichProcess.once('error', (error) => {
35+
logger.error('lich process error', { error });
36+
reject(error);
37+
});
38+
39+
sleep(lichWait! * 1000)
40+
.then(() => {
41+
resolve(lichProcess);
42+
})
43+
.catch((error) => {
44+
reject(error);
45+
});
46+
});
47+
48+
return {
49+
pid: lichProcess.pid,
50+
host: lichHost!,
51+
port: lichPort!,
52+
};
53+
};
54+
55+
const getLichArgs = (options: { gameCode: GameCode }): Array<string> => {
56+
const { gameCode } = options;
57+
58+
const lichArgs = ['--dragonrealms', '--genie'];
59+
60+
switch (gameCode) {
61+
case GameCode.PLATINUM:
62+
lichArgs.push('--platinum');
63+
break;
64+
case GameCode.FALLEN:
65+
lichArgs.push('--fallen');
66+
break;
67+
case GameCode.TEST:
68+
lichArgs.push('--test');
69+
break;
70+
}
71+
72+
return lichArgs;
73+
};

electron/main/lich/types.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Lich is a "man-in-the-middle" server that connects to the game server.
3+
* You connect to this host and port instead of the game server directly.
4+
*/
5+
export interface LichProcessInfo {
6+
/**
7+
* The process ID of the Lich process.
8+
*/
9+
pid?: number;
10+
/**
11+
* The host to connect to the Lich process.
12+
*/
13+
host: string;
14+
/**
15+
* The port to connect to the Lich process.
16+
*/
17+
port: number;
18+
}

0 commit comments

Comments
 (0)