activefiresmyloc (bounding box, published fields, out-of-range coordinates), activefiresunionmyloc (geoIntersects, including a shape larger than the viewport) and mysubscriptions (own subscriptions only, nothing for anonymous visitors).
200 lines
7.2 KiB
JavaScript
200 lines
7.2 KiB
JavaScript
/* 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);
|
|
});
|
|
});
|
|
});
|