mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 04:06:59 +00:00
fix: fall back to enroll token when pilot tunnel JWT is rejected (#1566)
* fix: fall back to enroll token when pilot tunnel JWT is rejected On HTTP 401/404 upgrade rejection, delete stale pilot.jwt and retry with SENCHO_ENROLL_TOKEN. * test: import pilot agent module after DATA_DIR is set in fallback test The auth-fallback test statically imported pilot/agent, which freezes its pilot.jwt path from DATA_DIR at module load, before setupTestDb redirects DATA_DIR to a writable temp dir. On the Linux CI runner the path resolved to a non-writable /app/data, so persistToken silently failed and the round-trip assertion read null. Import the module dynamically in beforeAll after setupTestDb, matching the sibling unit test. * fix: remove unexpected-response listener that blocked pilot reconnect The ws library skips abortHandshake when an unexpected-response listener exists, so error and close never fire and the agent hangs in CONNECTING. Detect auth rejection via the abortHandshake error message instead; the close handler already performs enroll-token fallback and reconnect.
This commit is contained in:
@@ -19,11 +19,12 @@
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
const { mockReadFileSync, mockWriteFileSync, mockExistsSync, mockMkdirSync } = vi.hoisted(() => ({
|
||||
const { mockReadFileSync, mockWriteFileSync, mockExistsSync, mockMkdirSync, mockUnlinkSync } = vi.hoisted(() => ({
|
||||
mockReadFileSync: vi.fn(),
|
||||
mockWriteFileSync: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
mockMkdirSync: vi.fn(),
|
||||
mockUnlinkSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('fs', () => {
|
||||
@@ -32,13 +33,14 @@ vi.mock('fs', () => {
|
||||
writeFileSync: mockWriteFileSync,
|
||||
existsSync: mockExistsSync,
|
||||
mkdirSync: mockMkdirSync,
|
||||
unlinkSync: mockUnlinkSync,
|
||||
};
|
||||
return { ...mock, default: mock };
|
||||
});
|
||||
|
||||
// agent.ts is imported AFTER vi.mock so the mock is in place when the
|
||||
// module's top-level fs import resolves.
|
||||
import { readPersistedToken, persistToken } from '../pilot/agent';
|
||||
import { readPersistedToken, persistToken, clearPersistedToken } from '../pilot/agent';
|
||||
|
||||
let errorSpy: ReturnType<typeof vi.spyOn>;
|
||||
let warnSpy: ReturnType<typeof vi.spyOn>;
|
||||
@@ -167,3 +169,25 @@ describe('persistToken', () => {
|
||||
expect(() => persistToken('test-token')).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearPersistedToken', () => {
|
||||
it('unlinks the token file on the happy path', () => {
|
||||
clearPersistedToken();
|
||||
expect(mockUnlinkSync).toHaveBeenCalledWith(expect.stringContaining('pilot.jwt'));
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not warn on ENOENT (file already absent)', () => {
|
||||
mockUnlinkSync.mockImplementationOnce(() => { throw fsError('ENOENT', 'no such file'); });
|
||||
clearPersistedToken();
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('warns on EACCES (read-only volume)', () => {
|
||||
mockUnlinkSync.mockImplementationOnce(() => { throw fsError('EACCES', 'permission denied'); });
|
||||
clearPersistedToken();
|
||||
expect(warnSpy).toHaveBeenCalledOnce();
|
||||
expect(String(warnSpy.mock.calls[0][0])).toContain('Failed to remove persisted tunnel token');
|
||||
expect(String(warnSpy.mock.calls[0][0])).toContain('EACCES');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user