Files
laude/src/main/meeting-detect.test.ts
2026-06-06 02:27:04 +07:00

116 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
/**
* Тесты эвристики «человек на ВКС». Мокаем `node:child_process.exec`
* (через него идёт `tasklist`), electron BrowserWindow (broadcast no-op) и
* logger. resetModules + dynamic import в каждом тесте — чтобы сбросить
* module-level кэш (`cachedActive`, `lastCheckAt`).
*/
type ExecCb = (err: Error | null, res?: { stdout: string }) => void
const h = vi.hoisted(() => ({
// Текущая реализация exec для конкретного теста.
execImpl: ((_cmd: string, _opts: unknown, cb: ExecCb) =>
cb(null, { stdout: '' })) as (
cmd: string,
opts: unknown,
cb: ExecCb
) => void,
calls: 0,
log: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }
}))
const originalPlatform = process.platform
vi.mock('node:child_process', () => ({
exec: (cmd: string, opts: unknown, cb: ExecCb) => {
h.calls += 1
h.execImpl(cmd, opts, cb)
}
}))
vi.mock('electron', () => ({ BrowserWindow: { getAllWindows: () => [] } }))
vi.mock('./logger', () => ({ log: h.log }))
/** CSV-строка tasklist для заданного набора .exe. */
function csv(...procs: string[]): string {
return procs.map((p) => `"${p}","1234","Console","1","85,432 K"`).join('\r\n')
}
async function load(): Promise<typeof import('./meeting-detect')> {
return import('./meeting-detect')
}
function setPlatform(platform: NodeJS.Platform): void {
Object.defineProperty(process, 'platform', {
value: platform,
configurable: true
})
}
beforeEach(() => {
vi.resetModules()
setPlatform('win32')
h.calls = 0
h.execImpl = (_cmd, _opts, cb) => cb(null, { stdout: '' })
h.log.info.mockClear()
h.log.warn.mockClear()
})
afterEach(() => {
setPlatform(originalPlatform)
vi.restoreAllMocks()
})
describe('isMeetingActive', () => {
it('детектит zoom.exe', async () => {
h.execImpl = (_c, _o, cb) => cb(null, { stdout: csv('zoom.exe') })
const { isMeetingActive } = await load()
expect(await isMeetingActive()).toBe(true)
})
it('детектит новые Teams (ms-teams.exe)', async () => {
h.execImpl = (_c, _o, cb) =>
cb(null, { stdout: csv('explorer.exe', 'ms-teams.exe') })
const { isMeetingActive } = await load()
expect(await isMeetingActive()).toBe(true)
})
it('возвращает false когда ВКС-процессов нет', async () => {
h.execImpl = (_c, _o, cb) =>
cb(null, { stdout: csv('explorer.exe', 'code.exe', 'chrome.exe') })
const { isMeetingActive } = await load()
expect(await isMeetingActive()).toBe(false)
})
it('кэширует результат в пределах CACHE_MS (exec вызывается один раз)', async () => {
h.execImpl = (_c, _o, cb) => cb(null, { stdout: csv('discord.exe') })
const { isMeetingActive } = await load()
await isMeetingActive()
await isMeetingActive()
expect(h.calls).toBe(1)
})
it('при падении tasklist возвращает false и логирует warn', async () => {
h.execImpl = (_c, _o, cb) => cb(new Error('ETIMEDOUT'))
const { isMeetingActive } = await load()
expect(await isMeetingActive()).toBe(false)
expect(h.log.warn).toHaveBeenCalled()
})
it('isMeetingActiveSync отражает последний известный результат', async () => {
h.execImpl = (_c, _o, cb) => cb(null, { stdout: csv('webex.exe') })
const mod = await load()
expect(mod.isMeetingActiveSync()).toBe(false) // до первого запроса
await mod.isMeetingActive()
expect(mod.isMeetingActiveSync()).toBe(true)
})
it('на не-Windows возвращает false без вызова tasklist', async () => {
setPlatform('linux')
const { isMeetingActive } = await load()
expect(await isMeetingActive()).toBe(false)
expect(h.calls).toBe(0)
})
})