2021-01-27 08:45:18 +03:00
|
|
|
import Urbit from '../src';
|
2021-07-20 08:21:32 +03:00
|
|
|
import 'jest';
|
2021-01-27 08:45:18 +03:00
|
|
|
|
2021-07-12 05:07:56 +03:00
|
|
|
function fakeSSE(messages = [], timeout = 0) {
|
|
|
|
const ourMessages = [...messages];
|
|
|
|
const enc = new TextEncoder();
|
|
|
|
return new ReadableStream({
|
|
|
|
start(controller) {
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
let message = ':\n';
|
|
|
|
if (ourMessages.length > 0) {
|
|
|
|
message = ourMessages.shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
controller.enqueue(enc.encode(message));
|
|
|
|
}, 50);
|
|
|
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
setTimeout(() => {
|
|
|
|
controller.close();
|
2021-07-26 03:04:26 +03:00
|
|
|
clearInterval(interval);
|
2021-07-12 05:07:56 +03:00
|
|
|
interval;
|
|
|
|
}, timeout);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const ship = '~sampel-palnet';
|
|
|
|
let eventId = 0;
|
|
|
|
function event(data: any) {
|
|
|
|
return `id:${eventId++}\ndata:${JSON.stringify(data)}\n\n`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function fact(id: number, data: any) {
|
|
|
|
return event({
|
|
|
|
response: 'diff',
|
|
|
|
id,
|
|
|
|
json: data,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function ack(id: number, err = false) {
|
|
|
|
const res = err ? { err: 'Error' } : { ok: true };
|
|
|
|
return event({ id, response: 'poke', ...res });
|
|
|
|
}
|
|
|
|
const fakeFetch = (body) => () =>
|
|
|
|
Promise.resolve({
|
|
|
|
ok: true,
|
|
|
|
body: body(),
|
|
|
|
});
|
|
|
|
|
|
|
|
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
|
|
|
|
2021-11-09 22:16:48 +03:00
|
|
|
process.on('unhandledRejection', (error) => {
|
2021-07-12 05:07:56 +03:00
|
|
|
console.error(error);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Initialisation', () => {
|
|
|
|
let airlock: Urbit;
|
|
|
|
let fetchSpy;
|
|
|
|
beforeEach(() => {
|
|
|
|
airlock = new Urbit('', '+code');
|
|
|
|
});
|
|
|
|
afterEach(() => {
|
|
|
|
fetchSpy.mockReset();
|
|
|
|
});
|
|
|
|
it('should poke & connect upon a 200', async () => {
|
|
|
|
airlock.onOpen = jest.fn();
|
|
|
|
fetchSpy = jest.spyOn(window, 'fetch');
|
|
|
|
fetchSpy
|
|
|
|
.mockImplementationOnce(() =>
|
|
|
|
Promise.resolve({ ok: true, body: fakeSSE() })
|
|
|
|
)
|
|
|
|
.mockImplementationOnce(() =>
|
2021-07-20 08:21:32 +03:00
|
|
|
Promise.resolve({ ok: true, body: fakeSSE([ack(1)]) })
|
2021-07-12 05:07:56 +03:00
|
|
|
);
|
|
|
|
await airlock.eventSource();
|
|
|
|
|
|
|
|
expect(airlock.onOpen).toHaveBeenCalled();
|
|
|
|
}, 500);
|
|
|
|
it('should handle failures', async () => {
|
|
|
|
fetchSpy = jest.spyOn(window, 'fetch');
|
2021-07-26 03:04:26 +03:00
|
|
|
airlock.onRetry = jest.fn();
|
|
|
|
airlock.onOpen = jest.fn();
|
2021-07-12 05:07:56 +03:00
|
|
|
fetchSpy
|
2021-07-20 08:21:32 +03:00
|
|
|
.mockImplementationOnce(() =>
|
|
|
|
Promise.resolve({ ok: true, body: fakeSSE() })
|
|
|
|
)
|
|
|
|
.mockImplementationOnce(() =>
|
2021-07-26 03:04:26 +03:00
|
|
|
Promise.resolve({ ok: true, body: fakeSSE([], 100) })
|
2021-11-09 22:16:48 +03:00
|
|
|
);
|
2021-07-20 08:21:32 +03:00
|
|
|
|
2021-07-12 05:07:56 +03:00
|
|
|
airlock.onError = jest.fn();
|
|
|
|
try {
|
2021-07-26 03:04:26 +03:00
|
|
|
airlock.eventSource();
|
|
|
|
await wait(200);
|
2021-07-12 05:07:56 +03:00
|
|
|
} catch (e) {
|
2021-07-26 03:04:26 +03:00
|
|
|
expect(airlock.onRetry).toHaveBeenCalled();
|
2021-07-12 05:07:56 +03:00
|
|
|
}
|
2021-07-26 03:04:26 +03:00
|
|
|
}, 300);
|
2021-07-12 05:07:56 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('subscription', () => {
|
|
|
|
let airlock: Urbit;
|
|
|
|
let fetchSpy: jest.SpyInstance;
|
|
|
|
beforeEach(() => {
|
|
|
|
eventId = 1;
|
|
|
|
});
|
|
|
|
afterEach(() => {
|
|
|
|
fetchSpy.mockReset();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should subscribe', async () => {
|
|
|
|
fetchSpy = jest.spyOn(window, 'fetch');
|
|
|
|
airlock = new Urbit('', '+code');
|
|
|
|
airlock.onOpen = jest.fn();
|
|
|
|
const params = {
|
|
|
|
app: 'app',
|
|
|
|
path: '/path',
|
|
|
|
err: jest.fn(),
|
|
|
|
event: jest.fn(),
|
|
|
|
quit: jest.fn(),
|
|
|
|
};
|
|
|
|
const firstEv = 'one';
|
|
|
|
const secondEv = 'two';
|
|
|
|
const events = (id) => [fact(id, firstEv), fact(id, secondEv)];
|
|
|
|
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE(events(1))));
|
|
|
|
|
|
|
|
await airlock.subscribe(params);
|
|
|
|
await wait(600);
|
|
|
|
|
|
|
|
expect(airlock.onOpen).toBeCalled();
|
|
|
|
expect(params.event).toHaveBeenNthCalledWith(1, firstEv);
|
|
|
|
expect(params.event).toHaveBeenNthCalledWith(2, secondEv);
|
|
|
|
}, 800);
|
|
|
|
it('should poke', async () => {
|
|
|
|
fetchSpy = jest.spyOn(window, 'fetch');
|
|
|
|
airlock = new Urbit('', '+code');
|
|
|
|
airlock.onOpen = jest.fn();
|
|
|
|
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE([ack(1)])));
|
|
|
|
const params = {
|
|
|
|
app: 'app',
|
|
|
|
mark: 'mark',
|
|
|
|
json: { poke: 1 },
|
|
|
|
onSuccess: jest.fn(),
|
|
|
|
onError: jest.fn(),
|
|
|
|
};
|
|
|
|
await airlock.poke(params);
|
|
|
|
await wait(300);
|
|
|
|
expect(params.onSuccess).toHaveBeenCalled();
|
|
|
|
}, 800);
|
|
|
|
|
|
|
|
it('should nack poke', async () => {
|
|
|
|
fetchSpy = jest.spyOn(window, 'fetch');
|
|
|
|
airlock = new Urbit('', '+code');
|
|
|
|
airlock.onOpen = jest.fn();
|
2021-07-26 03:04:26 +03:00
|
|
|
fetchSpy
|
|
|
|
.mockImplementationOnce(() =>
|
|
|
|
Promise.resolve({ ok: true, body: fakeSSE() })
|
|
|
|
)
|
|
|
|
.mockImplementationOnce(() =>
|
|
|
|
Promise.resolve({ ok: false, body: fakeSSE([ack(1, true)]) })
|
2021-11-09 22:16:48 +03:00
|
|
|
);
|
2021-07-26 03:04:26 +03:00
|
|
|
|
2021-07-12 05:07:56 +03:00
|
|
|
const params = {
|
|
|
|
app: 'app',
|
|
|
|
mark: 'mark',
|
|
|
|
json: { poke: 1 },
|
|
|
|
onSuccess: jest.fn(),
|
|
|
|
onError: jest.fn(),
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
await airlock.poke(params);
|
|
|
|
await wait(300);
|
|
|
|
} catch (e) {
|
2021-07-26 03:04:26 +03:00
|
|
|
expect(true).toBe(true);
|
2021-07-12 05:07:56 +03:00
|
|
|
}
|
2021-01-27 08:45:18 +03:00
|
|
|
});
|
|
|
|
});
|