test(publications): cover the geo publications the map calls on every pan
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).
This commit is contained in:
parent
755afccf90
commit
e61efc681c
1 changed files with 200 additions and 0 deletions
200
test/server/publications.test.js
Normal file
200
test/server/publications.test.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue