amaran-light-cli
Version:
Command line tool for controlling Aputure Amaran lights via WebSocket to a local Amaran desktop app.
176 lines • 7.9 kB
JavaScript
import { Command } from 'commander';
import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from 'vitest';
import registerCommands from '../commands.js';
// Mock dependencies
vi.mock('../daylightSimulation/geoipUtil', () => ({
getLocationFromIP: vi.fn(() => ({ ll: [37.7749, -122.4194] })),
}));
// We will mock cctUtil's return value individually in tests if needed,
// but here is a default mock factory
const mockCalculateCCT = vi.fn();
vi.mock('../daylightSimulation/cctUtil', () => ({
calculateCCT: (...args) => mockCalculateCCT(...args),
parseCurveType: vi.fn(() => 'HANN'),
CurveType: {
HANN: 'hann',
},
}));
describe('auto-cct max-lux logic', () => {
const originalFetch = global.fetch;
const mockFetch = vi.fn();
// Common stub setups
const setCCT = vi.fn();
const disconnect = vi.fn(async () => Promise.resolve());
const controllerStub = {
getDevices: vi.fn(() => [{ node_id: '400J5-F2C008', device_name: 'Test Light' }]),
getLightSleepStatus: vi.fn((_nodeId, cb) => {
setImmediate(() => cb(true, 'ok', { sleep: false }));
}),
setCCT,
disconnect,
};
const createController = async () => controllerStub;
const findDevice = (_controller, _deviceQuery) => null;
const asyncCommand = (fn) => (...args) => fn(...args);
beforeAll(() => {
mockFetch.mockImplementation(async () => ({
json: async () => ({ ip: '1.2.3.4' }),
}));
global.fetch = mockFetch;
});
afterEach(() => {
mockFetch.mockClear();
setCCT.mockClear();
disconnect.mockClear();
mockCalculateCCT.mockReset();
});
afterAll(() => {
global.fetch = originalFetch;
});
test('uses standard intensity when no max-lux provided', async () => {
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 500, lightOutput: 5000 }); // 50% intensity
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({}),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct']);
// Should use raw intensity: 500 (50.0%)
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 500);
});
test('uses max-lux scaling when CLI option provided', async () => {
// We mock calculateCCT to return what we expect it to return when max-lux is 10000.
// lightOutput 5000 lux. maxLux 10000. Should be 50% -> 500 raw.
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 500, lightOutput: 5000 });
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({}),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
// --max-lux 10000
await program.parseAsync(['node', 'test', 'auto-cct', '--max-lux', '10000']);
// Check that calculateCCT was called with maxLux
expect(mockCalculateCCT).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), expect.any(Date), expect.objectContaining({ maxLux: 10000 }), expect.any(String));
// Should use the intensity returned by the mock: 500 (50.0%)
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 500);
});
test('uses max-lux scaling from config', async () => {
// lightOutput 2000 lux. maxLux 10000. Should be 20% -> 200 raw.
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 200, lightOutput: 2000 });
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({ maxLux: 10000 }),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct']);
expect(mockCalculateCCT).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), expect.any(Date), expect.objectContaining({ maxLux: 10000 }), expect.any(String));
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 200);
});
test('CLI option overrides config for max-lux', async () => {
// Config: 10000 maxLux. CLI: 5000 maxLux. LightOutput: 2500.
// CLI should yield 50% -> 500 raw.
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 500, lightOutput: 2500 });
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({ maxLux: 10000 }),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct', '--max-lux', '5000']);
expect(mockCalculateCCT).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), expect.any(Date), expect.objectContaining({ maxLux: 5000 }), expect.any(String));
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 500);
});
test('falls back to intensity curve if lightOutput is missing', async () => {
// No lightOutput in result
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 300 }); // 30%
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({}),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct', '--max-lux', '10000']);
// Should use the 30% intensity since lightOutput is missing
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 300);
});
test('clamps intensity to 100% if lux exceeds max-lux', async () => {
// Output 15000, Max 10000 -> 150% -> clamped to 100% (in calculateCCT)
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 1000, lightOutput: 15000 });
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({}),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct', '--max-lux', '10000']);
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 1000); // 100%
});
test('respects intensityMin even when max-lux is set', async () => {
// lightOutput 100 lux. maxLux 10000. Scaling gives 1%.
// But intensityMin is 10%. Result should be 10% (100 raw).
// The calculateCCT call should return 100.
mockCalculateCCT.mockReturnValue({ cct: 5600, intensity: 100, lightOutput: 100 });
const deps = {
createController,
findDevice,
asyncCommand,
loadConfig: () => ({ intensityMin: 10 }),
saveWsUrl: undefined,
};
const program = new Command();
program.exitOverride();
registerCommands(program, deps);
await program.parseAsync(['node', 'test', 'auto-cct', '--max-lux', '10000']);
expect(mockCalculateCCT).toHaveBeenCalledWith(expect.any(Number), expect.any(Number), expect.any(Date), expect.objectContaining({ maxLux: 10000, intensityMinPct: 10 }), expect.any(String));
// Should be 100 (10.0%)
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 100);
});
});
//# sourceMappingURL=autoCctMaxLux.test.js.map