Files
hop.noname.company/apps/server/test/nearby.test.js

185 lines
7.1 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { io } from 'socket.io-client';
import { randomUUID } from 'node:crypto';
import { fixture } from './helpers.js';
import { metersBetween, NearbyLocations } from '../src/nearby.js';
const point = { latitude: 37.5665, longitude: 126.978, accuracy: 20 };
test('거리 계산과 동의한 양쪽 좌표, 반경·만료·여러 탭·변경 알림을 검사한다', () => {
const changes = [],
locations = new NearbyLocations(100, (ids) => changes.push(ids));
try {
assert.equal(metersBetween(point, point), 0);
assert(
metersBetween({ latitude: 0, longitude: 179.9999 }, { latitude: 0, longitude: -179.9999 }) <
30,
);
locations.set('a', 'a1', point);
assert.equal(changes.length, 0, '혼자서는 불필요한 목록 갱신을 하지 않음');
locations.set('b', 'b1', { ...point, latitude: point.latitude + 0.0004 });
assert.equal(locations.nearby('a', 'b'), true);
const before = changes.length;
locations.set('b', 'b1', { ...point, latitude: point.latitude + 0.0004 });
assert.equal(changes.length, before, '같은 위치를 갱신해도 목록 재전송 없음');
locations.set('a', 'a2', point);
locations.clear('a1');
assert.equal(locations.nearby('a', 'b'), true);
locations.clear('a2');
assert.equal(locations.nearby('a', 'b'), false);
locations.set('a', 'a1', point);
locations.points.get('a1').until = Date.now() - 1;
assert.equal(locations.nearby('a', 'b'), false);
locations.clear('a1');
assert(changes.at(-1).includes('b'));
locations.set('a', 'a1', { ...point, latitude: point.latitude + 0.01 });
assert.equal(locations.nearby('a', 'b'), false);
} finally {
locations.close();
}
});
async function positioned(f, ip) {
const client = await f.create(ip, false);
client.socket = io(f.url, {
transports: ['websocket'],
reconnection: false,
auth: { csrfToken: client.csrf },
extraHeaders: { Origin: f.cfg.appUrl, Cookie: client.cookie, 'X-Forwarded-For': ip },
});
await new Promise((resolve, reject) => {
client.socket.once('connect', resolve);
client.socket.once('connect_error', reject);
});
return client;
}
const publish = (client, position) =>
client.socket.timeout(3000).emitWithAck('location:update', position);
test('다른 네트워크도 양쪽 위치 허용 시 발견·전송하고 좌표는 목록과 DB에 공개하지 않는다', async () => {
const f = await fixture(false),
clients = [];
try {
const a = await positioned(f, '8.8.8.8'),
b = await positioned(f, '1.1.1.1'),
c = await positioned(f, '9.9.9.9');
clients.push(a, b, c);
const list = async (client) => (await f.call(client, '/discovery/devices')).json().devices;
assert.equal((await list(a)).length, 0);
assert.equal((await publish(a, point)).ok, true);
assert.equal((await list(a)).length, 0);
assert.equal((await publish(b, { ...point, latitude: point.latitude + 0.0004 })).ok, true);
assert.equal((await publish(c, { ...point, latitude: point.latitude + 0.01 })).ok, true);
assert.deepEqual(
(await list(a)).map((device) => device.id),
[b.deviceId],
);
assert.deepEqual((await list(a))[0].sources, ['nearby']);
const snapshot = await f.call(a, '/device/snapshot');
assert(!snapshot.body.includes('latitude') && !snapshot.body.includes('longitude'));
assert(
!f.sqlite
.prepare("SELECT sql FROM sqlite_master WHERE type='table'")
.all()
.some((row) => /latitude|longitude/i.test(row.sql)),
);
const request = await f.request(a, b, 'https://example.com/nearby');
assert.equal(request.statusCode, 200, request.body);
assert.equal(
(
await f.call(
b,
`/transfer-requests/${request.json().requestId}/accept`,
'POST',
{},
randomUUID(),
)
).statusCode,
200,
);
assert.equal(
(
await f.call(
a,
`/transfers/${request.json().requestId}/complete`,
'POST',
{ urls: ['https://example.com/nearby'] },
randomUUID(),
)
).statusCode,
200,
);
b.socket.emit('location:clear');
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal((await list(a)).length, 0);
assert.equal((await f.request(a, b, 'https://example.com/outside')).statusCode, 403);
} finally {
clients.forEach((client) => client.socket.disconnect());
await f.close();
}
});
test('기기 숨김·방 참여·세션 종료·부정확한 위치와 잘못된 위치를 검사한다', async () => {
const f = await fixture(false),
clients = [];
try {
const a = await positioned(f, '8.8.8.8'),
b = await positioned(f, '1.1.1.1');
clients.push(a, b);
assert.equal((await publish(a, { ...point, latitude: 91 })).ok, false);
assert.equal((await publish(a, { ...point, accuracy: 10000 })).code, 'LOCATION_INACCURATE');
await publish(a, point);
await publish(b, point);
await f.call(b, '/device/me', 'PATCH', { discoverable: false });
assert.equal(f.service.nearby.guests.has(f.service.guest(b.cookie.split('=')[1]).id), false);
assert.equal((await publish(b, point)).code, 'LOCATION_UNAVAILABLE');
await f.call(b, '/device/me', 'PATCH', { discoverable: true });
await f.space(a, b);
assert.equal((await publish(a, point)).code, 'LOCATION_UNAVAILABLE');
const d = await positioned(f, '9.9.9.9');
clients.push(d);
await publish(d, point);
assert.equal(
(await f.call(d, '/discovery/devices')).json().devices.length,
0,
'방 안 기기는 외부 위치 목록에 나오지 않음',
);
const guestId = f.service.guest(d.cookie.split('=')[1]).id;
d.socket.disconnect();
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(f.service.nearby.guests.has(guestId), false);
} finally {
clients.forEach((client) => client.socket.disconnect());
await f.close();
}
});
test('수락 클릭으로 새 탭이 열려 위치가 제거돼도 해당 요청만 완료할 수 있다', async () => {
const f = await fixture(false),
clients = [];
try {
const a = await positioned(f, '8.8.8.8'),
b = await positioned(f, '1.1.1.1');
clients.push(a, b);
await publish(a, point);
await publish(b, point);
const response = await f.request(a, b, 'https://example.com/accepted'),
id = response.json().requestId;
b.socket.emit('location:accept', { requestId: id });
b.socket.emit('location:clear');
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(
f.service.nearby.nearby(
f.service.guest(a.cookie.split('=')[1]).id,
f.service.guest(b.cookie.split('=')[1]).id,
),
false,
);
assert.equal(
(await f.call(b, `/transfer-requests/${id}/accept`, 'POST', {}, randomUUID())).statusCode,
200,
);
assert.equal(f.service.locationAcceptances.has(id), false);
} finally {
clients.forEach((client) => client.socket.disconnect());
await f.close();
}
});