/* eslint-env mocha */ /* eslint-disable func-names, prefer-arrow-callback */ /* eslint-disable import/no-absolute-path */ import { expect } from 'chai'; import { Meteor } from 'meteor/meteor'; import ActiveFires from '/imports/api/ActiveFires/ActiveFires'; import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion'; import Subscriptions from '/imports/api/Subscriptions/Subscriptions'; import '/imports/api/ActiveFires/server/publications'; import '/imports/api/ActiveFiresUnion/server/publications'; import '/imports/api/Subscriptions/server/publications'; // The map calls these three on every pan and zoom. They are also the publications // nobody could change with any confidence, because a wrong bounding box just // returns fewer fires and looks like "no fires around here". const handler = name => Meteor.server.publish_handlers[name]; const runPublication = async (name, context, ...args) => { const result = await handler(name).apply(context, args); return result; }; const fireAt = (lat, lon) => ({ ourid: { type: 'Point', coordinates: [lon, lat] }, lat, lon, type: 'viirs', when: new Date('2026-07-30T05:30:00Z'), scan: 0.5, track: 0.4 }); // A small square around (lat, lon). const squareAt = (lat, lon, size = 0.01) => ({ type: 'Polygon', coordinates: [[ [lon - size, lat - size], [lon + size, lat - size], [lon + size, lat + size], [lon - size, lat + size], [lon - size, lat - size] ]] }); const OVIEDO = { lat: 43.36, lon: -5.84 }; const MADRID = { lat: 40.41, lon: -3.7 }; // north-east lng/lat, south-west lng/lat, as the client sends them. const boxAround = (p, pad = 0.5) => [p.lon + pad, p.lat + pad, p.lon - pad, p.lat - pad]; describe('geo publications', function () { this.timeout(30000); beforeEach(async function () { await ActiveFires.removeAsync({}); await ActiveFiresUnion.removeAsync({}); await Subscriptions.removeAsync({}); }); after(async function () { await ActiveFires.removeAsync({}); await ActiveFiresUnion.removeAsync({}); await Subscriptions.removeAsync({}); }); describe('activefiresmyloc', function () { it('returns only the fires inside the requested box', async function () { await ActiveFires.insertAsync(fireAt(OVIEDO.lat, OVIEDO.lon)); await ActiveFires.insertAsync(fireAt(MADRID.lat, MADRID.lon)); const cursor = await runPublication('activefiresmyloc', { userId: null }, ...boxAround(OVIEDO), false); const fires = await cursor.fetchAsync(); expect(fires).to.have.lengthOf(1); expect(fires[0].lat).to.equal(OVIEDO.lat); }); it('publishes only the fields the map needs', async function () { await ActiveFires.insertAsync(fireAt(OVIEDO.lat, OVIEDO.lon)); const cursor = await runPublication('activefiresmyloc', { userId: null }, ...boxAround(OVIEDO), false); const [fire] = await cursor.fetchAsync(); expect(Object.keys(fire).sort()).to.deep.equal(['_id', 'lat', 'lon', 'scan', 'track', 'when']); }); it('returns nothing for a box with no fires in it', async function () { await ActiveFires.insertAsync(fireAt(MADRID.lat, MADRID.lon)); const cursor = await runPublication('activefiresmyloc', { userId: null }, ...boxAround(OVIEDO), false); expect(await cursor.countAsync()).to.equal(0); }); it('refuses coordinates outside the world', async function () { let threw = false; try { await runPublication('activefiresmyloc', { userId: null }, 200, 10, -200, -10, false); } catch (e) { threw = true; } expect(threw, 'a longitude of 200 was accepted').to.equal(true); }); it('refuses a non-boolean withMarks', async function () { let threw = false; try { await runPublication('activefiresmyloc', { userId: null }, ...boxAround(OVIEDO), 'yes'); } catch (e) { threw = true; } expect(threw).to.equal(true); }); }); describe('activefiresunionmyloc', function () { it('returns the union shapes that intersect the box', async function () { await ActiveFiresUnion.insertAsync({ centerid: { type: 'Point', coordinates: [OVIEDO.lon, OVIEDO.lat] }, shape: squareAt(OVIEDO.lat, OVIEDO.lon), when: new Date('2026-07-30T05:30:00Z') }); await ActiveFiresUnion.insertAsync({ centerid: { type: 'Point', coordinates: [MADRID.lon, MADRID.lat] }, shape: squareAt(MADRID.lat, MADRID.lon), when: new Date('2026-07-30T05:30:00Z') }); const cursor = await runPublication('activefiresunionmyloc', { userId: null }, ...boxAround(OVIEDO), false); const unions = await cursor.fetchAsync(); expect(unions).to.have.lengthOf(1); expect(unions[0].centerid.coordinates[0]).to.equal(OVIEDO.lon); }); // $geoIntersects, not $geoWithin: a shape bigger than the viewport still has // to be drawn, which is exactly what a zoomed-in user sees. it('returns a shape that only partially overlaps the box', async function () { await ActiveFiresUnion.insertAsync({ centerid: { type: 'Point', coordinates: [OVIEDO.lon, OVIEDO.lat] }, shape: squareAt(OVIEDO.lat, OVIEDO.lon, 2), when: new Date('2026-07-30T05:30:00Z') }); const cursor = await runPublication('activefiresunionmyloc', { userId: null }, ...boxAround(OVIEDO, 0.1), false); expect(await cursor.countAsync()).to.equal(1); }); it('returns nothing for a box far from every shape', async function () { await ActiveFiresUnion.insertAsync({ centerid: { type: 'Point', coordinates: [MADRID.lon, MADRID.lat] }, shape: squareAt(MADRID.lat, MADRID.lon), when: new Date('2026-07-30T05:30:00Z') }); const cursor = await runPublication('activefiresunionmyloc', { userId: null }, ...boxAround(OVIEDO), false); expect(await cursor.countAsync()).to.equal(0); }); it('refuses coordinates outside the world', async function () { let threw = false; try { await runPublication('activefiresunionmyloc', { userId: null }, 10, 100, -10, -100, false); } catch (e) { threw = true; } expect(threw).to.equal(true); }); }); describe('mysubscriptions', function () { const subFor = (owner, lat, lon) => ({ location: { lat, lon }, geo: { type: 'Point', coordinates: [lon, lat] }, distance: 10, owner, type: 'web' }); it('publishes only the subscriptions of the logged-in user', async function () { await Subscriptions.insertAsync(subFor('user-a', OVIEDO.lat, OVIEDO.lon)); await Subscriptions.insertAsync(subFor('user-a', MADRID.lat, MADRID.lon)); await Subscriptions.insertAsync(subFor('user-b', MADRID.lat, MADRID.lon)); const cursor = await runPublication('mysubscriptions', { userId: 'user-a' }); const subs = await cursor.fetchAsync(); expect(subs).to.have.lengthOf(2); subs.forEach(s => expect(s.owner).to.equal('user-a')); }); it('publishes nothing to an anonymous visitor', async function () { await Subscriptions.insertAsync(subFor('user-a', OVIEDO.lat, OVIEDO.lon)); const cursor = await runPublication('mysubscriptions', { userId: null }); expect(await cursor.countAsync()).to.equal(0); }); }); });