quality: web debt batch (tests runner, FireContainer, rate-limit, maps, i18n)
Independent-of-prod quality debt from PENDIENTE.md §2: - test: migrate broken Jest -> meteortesting:mocha. Tests rewritten to chai + Meteor 3 async APIs, moved to test/server/ (server-only). test/server/ 00-setup.test.js re-runs the collection2/accounts init that `meteor test` skips (no server/main.js). New comments method + mediaAnalyzers coverage. Dropped rest.test.js (removed meteor/http; covered by smoke/). 36 passing. - fix: scope FireContainer read by the URL _id on the archive route instead of a selector-less FiresCollection.findOne() (imports/ui/pages/Fires/Fires.js). - feat: rate-limit abusable publications via rateLimitSubscriptions (fireFrom* and comments.forReference 5/1000ms; geo subs 10/1000ms). - perf: append loading=async to the Google Maps loader URL (Gkeys.js). - deps: meteor-accounts-t9n 2.0 -> 2.6 (no gl build -> keep gl->es fallback, documented); add 3 missing gl/common.json keys (0 missing now). - deps: drop jest/babel/enzyme, add chai.
This commit is contained in:
parent
bc778bfd97
commit
6b6f02d92d
27 changed files with 602 additions and 7042 deletions
|
|
@ -51,3 +51,5 @@ quave:synced-cron
|
||||||
# lmachens:kadira
|
# lmachens:kadira
|
||||||
nimble:restivus@0.8.12
|
nimble:restivus@0.8.12
|
||||||
underscore@1.6.4
|
underscore@1.6.4
|
||||||
|
|
||||||
|
meteortesting:mocha # test driver (see `npm test` / `npm run test-watch`)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@ logging@1.3.5
|
||||||
mdg:geolocation@1.3.0
|
mdg:geolocation@1.3.0
|
||||||
meteor@2.0.2
|
meteor@2.0.2
|
||||||
meteor-base@1.5.2
|
meteor-base@1.5.2
|
||||||
|
meteortesting:browser-tests@1.8.0
|
||||||
|
meteortesting:mocha@3.3.0
|
||||||
|
meteortesting:mocha-core@8.2.0
|
||||||
minifier-css@2.0.0
|
minifier-css@2.0.0
|
||||||
minifier-js@3.0.1
|
minifier-js@3.0.1
|
||||||
minimongo@2.0.2
|
minimongo@2.0.2
|
||||||
|
|
|
||||||
46
PENDIENTE.md
46
PENDIENTE.md
|
|
@ -37,24 +37,34 @@ React 19: el `<Blaze serverFacts>` de Status.js (`findDOMNode`), solo en `/statu
|
||||||
|
|
||||||
## 2. Deuda funcional / de calidad de la web (independiente de prod)
|
## 2. Deuda funcional / de calidad de la web (independiente de prod)
|
||||||
|
|
||||||
- **Comentarios (feature React nueva): verificación manual en staging.** No la
|
- **Comentarios (feature React nueva): verificación manual en staging.** El smoke
|
||||||
cubre el smoke. Probar en una página de fuego: publicar/editar/borrar,
|
no llega a la UI. Los **métodos** (insert/edit/remove/like/dislike) y el embed
|
||||||
like/dislike, embed de imagen y YouTube, y el email a otros comentaristas.
|
(`mediaAnalyzers`) ya están cubiertos por mocha (`test/server/comments.test.js`),
|
||||||
- **`FireContainer` usa `FiresCollection.findOne()` sin selector**
|
pero quedan por probar a mano en una página de fuego:
|
||||||
(`imports/ui/pages/Fires/Fires.js`): puede devolver un doc de una suscripción
|
- publicar / editar / borrar un comentario (UI React)
|
||||||
previa. Endurecer filtrando por el `_id` de la URL. (Preexistente, no bloquea,
|
- like / dislike (toggle)
|
||||||
pero conviene antes de prod.)
|
- render del embed de imagen y de YouTube
|
||||||
- **Suite de tests sin runner.** El stack de test (meteortesting:mocha, chai…)
|
- email a otros comentaristas (`onCommentAdd.js`, entrega real de correo)
|
||||||
se eliminó en la migración 2.x (arrastraba coffeescript que crasheaba el build).
|
- ✅ **`FireContainer` endurecido** (`imports/ui/pages/Fires/Fires.js`): el
|
||||||
Quedan tests Jest en `test/` (`rest.test.js`, `email.test.js`, …) y
|
`findOne()` sin selector ahora se acota por el `_id` de la URL en la ruta
|
||||||
`npm test → jest`, pero hay que **verificar/actualizar** que pasan sobre el
|
`archive` (`new Meteor.Collection.ObjectID(id)`); en active/alert/hash cada
|
||||||
código async de Meteor 3. Hoy la ÚNICA red automática es `smoke/`.
|
publicación deja un único fire en minimongo, así que el read vacío es correcto.
|
||||||
- **Rate-limit de publications** pendiente (`imports/startup/server/api.js:18`).
|
- ✅ **Suite de tests con runner (`meteortesting:mocha`).** `npm test` corre
|
||||||
- **Google Maps** se carga sin `loading=async` (aviso de rendimiento); el loader
|
`meteor test … --driver-package meteortesting:mocha` (watch: `npm run test-watch`).
|
||||||
`google-maps` npm es viejo → valorar el loader async moderno.
|
Tests reescritos a `chai` + APIs async de Meteor 3 en `test/server/*.test.js`
|
||||||
- **i18n de cuentas (`meteor-accounts-t9n`, T9n):** paquete antiguo aún usado
|
(server-only), incluyendo cobertura nueva de los métodos de comentarios. Jest y
|
||||||
(Login, VerifyEmail, i18n). Verificar que funciona en Meteor 3; traducción
|
`rest.test.js` (ya cubierto por `smoke/`) eliminados. **36 passing.** Pendiente
|
||||||
gallega (gl) incompleta (varios TODO en `i18n.js`).
|
menor: portar los casos de token inválido (401/400) de `rest.test.js` al smoke.
|
||||||
|
- ✅ **Rate-limit de publications** (`imports/startup/server/api.js`): reglas
|
||||||
|
`type: 'subscription'` vía `rateLimitSubscriptions` — 5/1000ms en `fireFrom*` y
|
||||||
|
`comments.forReference`; 10/1000ms en las subs geo (map pan/zoom).
|
||||||
|
- ✅ **Google Maps con `loading=async`** (`imports/startup/client/Gkeys.js`):
|
||||||
|
se envuelve `GoogleMapsLoader.createUrl` para añadir el parámetro (el paquete
|
||||||
|
`google-maps` npm no expone hook). *Verificar en staging que el mapa carga y el
|
||||||
|
aviso de consola desaparece.*
|
||||||
|
- ✅ **`meteor-accounts-t9n` actualizado** a `^2.6.0` (es/en OK). El paquete sigue
|
||||||
|
sin build gallego (`gl`), así que se mantiene el fallback gl→es documentado en
|
||||||
|
`i18n.js`. Traducción gallega de la app (`gl/common.json`): 0 claves faltantes.
|
||||||
- **Salto Bootstrap 4→5 (CSS/JS) pendiente:** react-bootstrap ya está en v2 y
|
- **Salto Bootstrap 4→5 (CSS/JS) pendiente:** react-bootstrap ya está en v2 y
|
||||||
corre sobre el CSS Bootstrap 4 actual (`alexwine:bootstrap-4`) con un único
|
corre sobre el CSS Bootstrap 4 actual (`alexwine:bootstrap-4`) con un único
|
||||||
shim `.form-label` en `forms.scss`. El salto a Bootstrap 5 npm está diferido
|
shim `.form-label` en `forms.scss`. El salto a Bootstrap 5 npm está diferido
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,15 @@ export default ({ methods, limit, timeRange }) => {
|
||||||
}, limit, timeRange);
|
}, limit, timeRange);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Same idea for subscriptions. DDPRateLimiter matches subscribe calls only when
|
||||||
|
// the rule pins `type: 'subscription'`, so this is a separate helper.
|
||||||
|
export const rateLimitSubscriptions = ({ subscriptions, limit, timeRange }) => {
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
DDPRateLimiter.addRule({
|
||||||
|
type: 'subscription',
|
||||||
|
name(name) { return subscriptions.indexOf(name) > -1; },
|
||||||
|
connectionId() { return true; },
|
||||||
|
}, limit, timeRange);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ class GkeysC {
|
||||||
// console.log(google.maps);
|
// console.log(google.maps);
|
||||||
GoogleMapsLoader.KEY = key;
|
GoogleMapsLoader.KEY = key;
|
||||||
GoogleMapsLoader.LIBRARIES = ['places'];
|
GoogleMapsLoader.LIBRARIES = ['places'];
|
||||||
|
// The google-maps npm package (v3.2.1) builds the script URL without
|
||||||
|
// `loading=async`, which Google now warns about. It exposes no hook for
|
||||||
|
// extra params, so wrap createUrl to append it (no node_modules edit).
|
||||||
|
const origCreateUrl = GoogleMapsLoader.createUrl;
|
||||||
|
GoogleMapsLoader.createUrl = () => `${origCreateUrl()}&loading=async`;
|
||||||
GoogleMapsLoader.load(() => {
|
GoogleMapsLoader.load(() => {
|
||||||
self.gmapkey.set(key);
|
self.gmapkey.set(key);
|
||||||
console.log('GMaps script just loaded');
|
console.log('GMaps script just loaded');
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ if (sendMissing && Meteor.isDevelopment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setT9(lang) {
|
function setT9(lang) {
|
||||||
|
// meteor-accounts-t9n has no Galician build, so use Spanish for gl account
|
||||||
|
// error messages (the closest shipped language). See common/i18n.js.
|
||||||
if (lang === 'gl') {
|
if (lang === 'gl') {
|
||||||
T9n.setLanguage('es');
|
T9n.setLanguage('es');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import moment from 'moment';
|
||||||
// Load the js langs
|
// Load the js langs
|
||||||
import es from 'meteor-accounts-t9n/build/es';
|
import es from 'meteor-accounts-t9n/build/es';
|
||||||
import en from 'meteor-accounts-t9n/build/en';
|
import en from 'meteor-accounts-t9n/build/en';
|
||||||
// TODO ask for translation of this
|
// meteor-accounts-t9n (2.6.0) ships no Galician build (build/gl.js), so account
|
||||||
|
// error messages fall back to Spanish for gl — see setT9() in client/i18n.js.
|
||||||
|
// Re-enable this import if the upstream package ever adds a gl translation.
|
||||||
// import gl from 'meteor-accounts-t9n/build/gl';
|
// import gl from 'meteor-accounts-t9n/build/gl';
|
||||||
|
|
||||||
const backOpts = {
|
const backOpts = {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ import '../../api/FireAlerts/server/publications';
|
||||||
import '../../api/Subscriptions/methods';
|
import '../../api/Subscriptions/methods';
|
||||||
import '../../api/Subscriptions/server/publications';
|
import '../../api/Subscriptions/server/publications';
|
||||||
|
|
||||||
// TODO add rate-limit to these publications
|
|
||||||
|
|
||||||
import '../../api/Notifications/methods';
|
import '../../api/Notifications/methods';
|
||||||
import '../../api/Notifications/server/publications';
|
import '../../api/Notifications/server/publications';
|
||||||
|
|
||||||
|
|
@ -28,3 +26,26 @@ import '../../api/SiteSettings/server/publications';
|
||||||
|
|
||||||
import '../../api/FalsePositives/methods';
|
import '../../api/FalsePositives/methods';
|
||||||
import '../../api/FalsePositives/server/publications';
|
import '../../api/FalsePositives/server/publications';
|
||||||
|
|
||||||
|
import { rateLimitSubscriptions } from '../../modules/rate-limit';
|
||||||
|
|
||||||
|
// Rate-limit the abusable publications (the old TODO above). Single-fire
|
||||||
|
// lookups and the comments feed are cheap but brute-forceable, so keep them
|
||||||
|
// tight; the geo subs fire on every map pan/zoom, so give them more headroom.
|
||||||
|
rateLimitSubscriptions({
|
||||||
|
subscriptions: [
|
||||||
|
'fireFromHash', 'fireFromAlertId', 'fireFromActiveId', 'fireFromId',
|
||||||
|
'comments.forReference'
|
||||||
|
],
|
||||||
|
limit: 5,
|
||||||
|
timeRange: 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimitSubscriptions({
|
||||||
|
subscriptions: [
|
||||||
|
'activefiresmyloc', 'activefiresunionmyloc', 'fireAlerts',
|
||||||
|
'falsePositivesMyloc', 'industriesMyloc'
|
||||||
|
],
|
||||||
|
limit: 10,
|
||||||
|
timeRange: 1000
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,22 @@ const FireContainer = withTracker(({ match }) => {
|
||||||
// console.log(`Type of '${fireType}' fire, active: ${active}, archive: ${archive}, fromHash: ${fromHash}`);
|
// console.log(`Type of '${fireType}' fire, active: ${active}, archive: ${archive}, fromHash: ${fromHash}`);
|
||||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||||
const loading = !subscription.ready();
|
const loading = !subscription.ready();
|
||||||
const notfound = !loading && FiresCollection.find().count() === 0;
|
// Scope the client-side read to the fire named in the URL. Only the archive
|
||||||
|
// route's param is a Fires `_id` (as 24-char hex, per hexId()); active/alert
|
||||||
|
// params are ActiveFire/Alert ids and hash is an encrypted blob, so their
|
||||||
|
// publications each put exactly one Fires doc in minimongo and an empty
|
||||||
|
// selector is the correct read. Scoping archive by _id stops a lingering doc
|
||||||
|
// from a previously-viewed fire being returned by a selector-less findOne().
|
||||||
|
let selector = {};
|
||||||
|
if (archive && id) {
|
||||||
|
try {
|
||||||
|
selector = new Meteor.Collection.ObjectID(id);
|
||||||
|
} catch (e) {
|
||||||
|
selector = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fire = FiresCollection.findOne(selector);
|
||||||
|
const notfound = !loading && !fire;
|
||||||
/* console.log(`loading fire: ${loading}`);
|
/* console.log(`loading fire: ${loading}`);
|
||||||
* console.log(`Not found fire: ${notfound}`); */
|
* console.log(`Not found fire: ${notfound}`); */
|
||||||
const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap);
|
const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap);
|
||||||
|
|
@ -302,9 +317,9 @@ const FireContainer = withTracker(({ match }) => {
|
||||||
fromHash,
|
fromHash,
|
||||||
falsePositives,
|
falsePositives,
|
||||||
industries,
|
industries,
|
||||||
fire: FiresCollection.findOne(),
|
fire,
|
||||||
notfound,
|
notfound,
|
||||||
when: subscription.ready() && FiresCollection.findOne() ? FiresCollection.findOne().when : null
|
when: fire ? fire.when : null
|
||||||
};
|
};
|
||||||
})(Fire);
|
})(Fire);
|
||||||
|
|
||||||
|
|
|
||||||
6469
package-lock.json
generated
6469
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
|
@ -3,7 +3,8 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://fuegos:fuegos@localhost:27017/fuegos meteor --settings settings-development.json",
|
"start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://fuegos:fuegos@localhost:27017/fuegos meteor --settings settings-development.json",
|
||||||
"test": "jest"
|
"test": "meteor test --once --driver-package meteortesting:mocha --settings settings-development.json --port 3100",
|
||||||
|
"test-watch": "TEST_WATCH=1 meteor test --driver-package meteortesting:mocha --settings settings-development.json --port 3100"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.29.7",
|
"@babel/runtime": "^7.29.7",
|
||||||
|
|
@ -45,7 +46,7 @@
|
||||||
"loms.perlin": "^1.0.1",
|
"loms.perlin": "^1.0.1",
|
||||||
"map-common-utils": "^0.5.0",
|
"map-common-utils": "^0.5.0",
|
||||||
"maxmind": "^2.3.0",
|
"maxmind": "^2.3.0",
|
||||||
"meteor-accounts-t9n": "^2.0.3",
|
"meteor-accounts-t9n": "^2.6.0",
|
||||||
"meteor-node-stubs": "^0.3.2",
|
"meteor-node-stubs": "^0.3.2",
|
||||||
"modernizr": "^3.6.0",
|
"modernizr": "^3.6.0",
|
||||||
"moment": "^2.19.1",
|
"moment": "^2.19.1",
|
||||||
|
|
@ -81,11 +82,8 @@
|
||||||
"twit": "^2.2.9"
|
"twit": "^2.2.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-jest": "^21.2.0",
|
|
||||||
"babel-plugin-lodash": "^3.2.11",
|
"babel-plugin-lodash": "^3.2.11",
|
||||||
"babel-preset-env": "^1.6.1",
|
"chai": "^4.5.0",
|
||||||
"babel-preset-react": "^6.24.1",
|
|
||||||
"enzyme": "^3.1.0",
|
|
||||||
"eslint": "^4.10.0",
|
"eslint": "^4.10.0",
|
||||||
"eslint-config-airbnb": "^16.1.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"eslint-import-resolver-meteor": "^0.4.0",
|
"eslint-import-resolver-meteor": "^0.4.0",
|
||||||
|
|
@ -93,7 +91,6 @@
|
||||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||||
"eslint-plugin-meteor": "^4.1.6",
|
"eslint-plugin-meteor": "^4.1.6",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"eslint-plugin-react": "^7.4.0",
|
||||||
"jest": "^21.2.1",
|
|
||||||
"sass": "^1.101.0"
|
"sass": "^1.101.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"fireDetected": "lume detectado {{when}}",
|
"fireDetected": "lume detectado {{when}}",
|
||||||
"fireDetectedAt": "lume detectado o {{when}}",
|
"fireDetectedAt": "lume detectado o {{when}}",
|
||||||
|
"Alerta de fuego": "Alerta de lume",
|
||||||
|
"Actualizado <1></1>.": "Actualizado <1></1>.",
|
||||||
|
"Actualizando...": "Actualizando...",
|
||||||
"Sí": "Si",
|
"Sí": "Si",
|
||||||
"No": "Non",
|
"No": "Non",
|
||||||
"AppName": "Tod@s contra o Lume",
|
"AppName": "Tod@s contra o Lume",
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
|
|
||||||
|
|
||||||
const centerid = { type: 'Point', coordinates: [-5.905956029891968, 43.55727622097011] };
|
|
||||||
const shape = { type: 'Polygon', coordinates: [[[-5.9084, 43.5558], [-5.906, 43.5558], [-5.906, 43.5566], [-5.9036, 43.5566], [-5.9036, 43.5583], [-5.9061, 43.5583], [-5.9061, 43.558], [-5.9084, 43.558], [-5.9084, 43.5558]]] };
|
|
||||||
const fireUnion = { centerid, shape, when: Date('2018-05-02T16:11:04.617Z') };
|
|
||||||
|
|
||||||
describe('activeFireUnion insert', () => {
|
|
||||||
it('should insert fire union', () => {
|
|
||||||
// Remove previous test register
|
|
||||||
const alreadyInserted = ActiveFiresUnion.findOne({ centerid });
|
|
||||||
if (alreadyInserted) {
|
|
||||||
ActiveFiresUnion.remove(alreadyInserted._id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertedId = ActiveFiresUnion.insert(fireUnion);
|
|
||||||
ActiveFiresUnion.schema.validate(fireUnion);
|
|
||||||
|
|
||||||
const inserted = ActiveFiresUnion.findOne(insertedId);
|
|
||||||
delete inserted._id;
|
|
||||||
chai.expect(fireUnion).to.deep.equal(inserted);
|
|
||||||
ActiveFiresUnion.remove(insertedId);
|
|
||||||
chai.expect(ActiveFiresUnion.find({ _id: inserted }).count()).equal(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import { subjectTruncate } from '/imports/modules/server/send-email';
|
|
||||||
|
|
||||||
describe('check email utilities', () => {
|
|
||||||
it('small words', async () => {
|
|
||||||
chai.expect('something').equal(subjectTruncate.apply('something', [50, true]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('big sentences', async () => {
|
|
||||||
chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.')
|
|
||||||
.equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('big sentences space break', async () => {
|
|
||||||
chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi...')
|
|
||||||
.equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('big sentences no break', async () => {
|
|
||||||
chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis...')
|
|
||||||
.equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.', [70, false]));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
|
||||||
import urlEnc from '/imports/modules/url-encode';
|
|
||||||
import seeder from '@cleverbeagle/seeder';
|
|
||||||
import { Accounts } from 'meteor/accounts-base';
|
|
||||||
|
|
||||||
const firesSeed = () => ({
|
|
||||||
collection: ActiveFiresCollection,
|
|
||||||
environments: ['development', 'staging'],
|
|
||||||
noLimit: true,
|
|
||||||
modelCount: 5,
|
|
||||||
model(dataIndex) {
|
|
||||||
return {
|
|
||||||
ourid: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: [
|
|
||||||
149.751 + dataIndex,
|
|
||||||
-10.021 + dataIndex
|
|
||||||
]
|
|
||||||
},
|
|
||||||
type: 'modis',
|
|
||||||
lat: -10.021 + dataIndex,
|
|
||||||
lon: 149.751 + dataIndex,
|
|
||||||
updatedAt: Date('2018-05-02T16:11:04.617Z'),
|
|
||||||
acq_date: '2018-05-02',
|
|
||||||
acq_time: '00:05',
|
|
||||||
scan: 1.5,
|
|
||||||
track: 1.2,
|
|
||||||
satellite: 'T',
|
|
||||||
confidence: 57,
|
|
||||||
version: '6.0NRT',
|
|
||||||
frp: 7.8,
|
|
||||||
daynight: 'D',
|
|
||||||
brightness: 304.3,
|
|
||||||
bright_t31: 283.4,
|
|
||||||
when: Date('2018-05-01T22:05:00.000Z'),
|
|
||||||
createdAt: Date('2018-05-02T16:11:04.617Z')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function encAndDec(obj) {
|
|
||||||
delete obj._id;
|
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
const w = unsealed.when;
|
|
||||||
unsealed.when = new Date(w);
|
|
||||||
const c = unsealed.createdAt;
|
|
||||||
unsealed.createdAt = new Date(c);
|
|
||||||
const u = unsealed.updatedAt;
|
|
||||||
unsealed.updatedAt = new Date(u);
|
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('url encoding', () => {
|
|
||||||
it('should encrypt and dcrypt basic objects', async (done) => {
|
|
||||||
const obj = { lat: 2 };
|
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encrypt and dcrypt objects with date', async (done) => {
|
|
||||||
const obj = { lat: 40.234503, lon: -3.350386, when: (new Date()).toString() };
|
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encrypt complete objects', async (done) => {
|
|
||||||
const obj = {
|
|
||||||
ourid: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: [
|
|
||||||
24.813,
|
|
||||||
9.223
|
|
||||||
]
|
|
||||||
},
|
|
||||||
type: 'modis',
|
|
||||||
lat: 9.223,
|
|
||||||
lon: 24.813,
|
|
||||||
when: new Date('2017-12-13T00:00:00.000Z').toISOString(),
|
|
||||||
scan: 1,
|
|
||||||
track: 1,
|
|
||||||
satellite: 'A',
|
|
||||||
confidence: 'nominal',
|
|
||||||
version: '6.0NRT',
|
|
||||||
frp: 6.5,
|
|
||||||
daynight: null,
|
|
||||||
brightness: 305.9,
|
|
||||||
bright_t31: 292.6
|
|
||||||
};
|
|
||||||
|
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
|
||||||
const sealed2 = await urlEnc.encrypt(obj);
|
|
||||||
// console.log(encodeURI(sealed));
|
|
||||||
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
|
||||||
chai.expect(sealed).to.equal(encodeURI(sealed));
|
|
||||||
chai.expect(sealed).to.not.equal(sealed2);
|
|
||||||
chai.expect(sealed).to.not.include('/');
|
|
||||||
chai.expect(sealed).to.not.include('%');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// This fails because Date is return as String (not as Date) and because _id
|
|
||||||
it('should encrypt and dcrypt collection objects', async (done) => {
|
|
||||||
seeder(ActiveFiresCollection, firesSeed());
|
|
||||||
const obj = ActiveFiresCollection.findOne();
|
|
||||||
encAndDec(obj);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decrypt node-red enc objects', async () => {
|
|
||||||
const sealed = 'Fe26.2**7ae64199b871901d16a6ba031198c57b43b4a9231e15b851ff80014a8de230ca*RkB3_Uu_oJnTefyzB-Ocqg*2Z9rXYf--GxLJnYESHvdK5rC6lOMlSbMJ6YQyR2Mgpl57iBYopAHPWRLNTBgNkbUHFYa0IlEjdyeEz5VvLDtVvlSr1Sam-Cx8GUai4mharZO5-Wkze0dTr7M56WofSC9jpkv3kANrpsrit3HASE_E-5Z1JVYQVQqehw-VbSZTorrYj0GCJiAgXE5I0iY4boXRDYCv7fm_pzcuAbHlnNkKT1GbMFEPjyVViP4mVMRI5PDUhcWb0f62goMX4UnYLZXum4oYOIr84DG8AxqIdW2L94yslt0EZkD3yVhvKEGoauMpy6ry2illpSgaMaj4sU3AKWIjfAsJXvM6bHwbNh6G1_BJfCapuu3KijBBQPQMAnhYuwLAlY56WN-Y2efNIBJkp__kO6ak2nFqu8PufpxE-cv0uW7x6GWUtCpnFO2SeUJWVVMddUgJjLiM8Hkdl1v9huB0WdIvsoPEV0ZOwHZaC3jtdKFSO7Xe6LdqTXjXmdBMwtOv-HefyaB8LnEN6dAeKWwJuDu7g03QFTryDIiuwtpQ8mcLWTJGHcxoaluNhUESOBULkWSv_E1vOM1_fFv**1e41101b27c5da58bc11177448e91d88d7801bf911088a895844db0c542dfa7c*dPlOhtXJcyR47ezuL7CEgC2Z8d6C1asNOiqZmhD5TXY';
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
// console.log(unsealed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encrypt and dcrypt Accounts hashes', async (done) => {
|
|
||||||
const token = Accounts._generateStampedLoginToken();
|
|
||||||
const obj = Accounts._hashStampedToken(token);
|
|
||||||
console.log(obj);
|
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
|
||||||
const w = unsealed.when;
|
|
||||||
unsealed.when = new Date(w);
|
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// limit 0 for no limit and test everything (and increase timeout)
|
|
||||||
it('should encrypt and dcrypt all collection objects', async (done) => {
|
|
||||||
seeder(ActiveFiresCollection, firesSeed());
|
|
||||||
ActiveFiresCollection.find({}, { limit: 1000 }).fetch().forEach((obj) => {
|
|
||||||
encAndDec(obj);
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
}).timeout(5000);
|
|
||||||
});
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
|
|
||||||
|
|
||||||
describe('get file of page', () => {
|
|
||||||
it('lang es retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'es'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang en retrieve en', async () => {
|
|
||||||
chai.expect('pages/about-en.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'en'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang gl retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'gl'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang ast retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'gl'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang eu retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'eu'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang ca retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'ca'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lang es-PE retrieve es', async () => {
|
|
||||||
chai.expect('pages/about-es.md').to.deep.equal(getFileNameOfLang('pages', 'about', 'md', 'es-PE'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import { HTTP } from 'meteor/http';
|
|
||||||
|
|
||||||
const testPort = process.env.TEST_PORT;
|
|
||||||
function url(path) {
|
|
||||||
return `http://127.0.0.1:${testPort}/${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('basic api v1 returns', () => {
|
|
||||||
it('should return uptime', async done =>
|
|
||||||
HTTP.get(url('api/v1/status/uptime'), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).to.equal(200);
|
|
||||||
chai.expect(typeof result.data.ms).to.equal('number');
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should return last-fire', async done =>
|
|
||||||
HTTP.get(url('api/v1/status/active-fires-count'), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
chai.expect(typeof result.data.total).to.equal('number');
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
const token = Meteor.settings.private.internalApiToken;
|
|
||||||
|
|
||||||
it('should return fires in some location', async done =>
|
|
||||||
HTTP.get(url(`api/v1/fires-in/${token}/38.736946/-9.142685/100`), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
chai.expect(typeof result.data.total).to.equal('number');
|
|
||||||
chai.expect(typeof result.data.real).to.equal('number');
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should return full fires in some location', async done =>
|
|
||||||
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/100`), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
chai.expect(typeof result.data.total).to.equal('number');
|
|
||||||
chai.expect(typeof result.data.real).to.equal('number');
|
|
||||||
chai.expect(typeof result.data.falsePos).to.equal('object');
|
|
||||||
chai.expect(typeof result.data.industries).to.equal('object');
|
|
||||||
chai.expect(typeof result.data.fires).to.equal('object');
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not return full fires with some wrong token', async done =>
|
|
||||||
HTTP.get(url('api/v1/fires-in-full/token/38.736946/-9.142685/100'), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not return fires with some wrong token', async done =>
|
|
||||||
HTTP.get(url('api/v1/fires-in/token/38.736946/-9.142685/100'), {
|
|
||||||
data: {
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not return fires with some wrong distance', async done =>
|
|
||||||
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/1100`), (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(400);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mobileToken = 'user-mobile-firebase-token';
|
|
||||||
let testUserId;
|
|
||||||
let returnedSubsId;
|
|
||||||
const objId = new Meteor.Collection.ObjectID();
|
|
||||||
|
|
||||||
it('should create mobile users', async (done) => {
|
|
||||||
// this are removed in the test database but we are testing in dev/ci database
|
|
||||||
|
|
||||||
// Delete the test account if it's still present
|
|
||||||
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
|
||||||
|
|
||||||
HTTP.post(url('api/v1/mobile/users'), {
|
|
||||||
data: {
|
|
||||||
token,
|
|
||||||
mobileToken,
|
|
||||||
lang: 'en'
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
chai.expect(jsendResult.data.upsertResult.numberAffected).to.equal(1);
|
|
||||||
chai.expect(jsendResult.data.mobileToken).to.equal(mobileToken);
|
|
||||||
chai.expect(jsendResult.data.lang).to.equal('en');
|
|
||||||
chai.expect(typeof jsendResult.data.username).to.equal('string');
|
|
||||||
testUserId = jsendResult.data.userId;
|
|
||||||
chai.expect(typeof testUserId).to.equal('string');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create mobile users with wrong token', async (done) => {
|
|
||||||
// this are removed in the test database but we are testing in dev/ci database
|
|
||||||
// Delete the test account if it's still present
|
|
||||||
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
|
||||||
|
|
||||||
HTTP.post(url('api/v1/mobile/users'), {
|
|
||||||
data: {
|
|
||||||
token: 'wrong token',
|
|
||||||
mobileToken,
|
|
||||||
lang: 'en'
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function addSubs(done) {
|
|
||||||
// this are removed in the test database but we are testing in dev/ci database
|
|
||||||
// Remove previous subs of this user
|
|
||||||
// Subscriptions.remove({ owner: testUserId });
|
|
||||||
// chai.expect(Subscriptions.find({ owner: testUserId }).count()).to.equal(0);
|
|
||||||
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
|
||||||
data: {
|
|
||||||
token,
|
|
||||||
mobileToken,
|
|
||||||
id: objId._str,
|
|
||||||
lat: 3.106111,
|
|
||||||
lon: 53.5775,
|
|
||||||
distance: 100
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
returnedSubsId = jsendResult.data.subsId;
|
|
||||||
chai.expect(returnedSubsId.toString()).equal(objId._str);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should create mobile user subscriptions', async (done) => {
|
|
||||||
addSubs(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create mobile user subscriptions with wrong token', async (done) => {
|
|
||||||
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
|
||||||
data: {
|
|
||||||
token: 'wrongOne',
|
|
||||||
mobileToken,
|
|
||||||
id: objId._str,
|
|
||||||
lat: 3.106111,
|
|
||||||
lon: 53.5775,
|
|
||||||
distance: 100
|
|
||||||
}
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 401) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get all mobile user subscriptions', async (done) => {
|
|
||||||
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
|
||||||
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
|
||||||
if (jsendResult.data.count !== 1) console.log(result);
|
|
||||||
chai.expect(jsendResult.data.count).to.equal(1);
|
|
||||||
// console.log(JSON.stringify(jsendResult.data.subscriptions));
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should remove mobile user subscriptions', async (done) => {
|
|
||||||
// this are removed in the test database but we are testing in dev/ci database
|
|
||||||
|
|
||||||
HTTP.del(url(`api/v1/mobile/subscriptions/${token}/${mobileToken}/${returnedSubsId}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get all mobile user subscriptions after deletion', async (done) => {
|
|
||||||
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
|
||||||
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
|
||||||
if (jsendResult.data.count !== 0) console.log(result);
|
|
||||||
chai.expect(jsendResult.data.count).to.equal(0);
|
|
||||||
// console.log(JSON.stringify(jsendResult.data.subscriptions));
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove mobile user subscriptions with wrong token', async (done) => {
|
|
||||||
HTTP.del(url(`api/v1/mobile/subscriptions/wrongOne/${mobileToken}/${returnedSubsId}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 401) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not get mobile user subscriptions with wrong token', async (done) => {
|
|
||||||
HTTP.get(url(`api/v1/mobile/subscriptions/all/wrongOne/${mobileToken}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 401) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(401);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get all mobile user subscriptions', async (done) => {
|
|
||||||
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
// console.log(result);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
|
||||||
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
|
||||||
if (jsendResult.data.count !== 0) console.log(result);
|
|
||||||
chai.expect(jsendResult.data.count).to.equal(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should del all mobile user subscriptions', async (done) => {
|
|
||||||
// Add subs
|
|
||||||
addSubs(() => {
|
|
||||||
HTTP.del(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
|
||||||
}, (error, result) => {
|
|
||||||
chai.expect(error, null);
|
|
||||||
if (result.statusCode !== 200) console.log(result);
|
|
||||||
chai.expect(result.statusCode).equal(200);
|
|
||||||
const jsendResult = result.data;
|
|
||||||
chai.expect(jsendResult.status).equal('success');
|
|
||||||
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
|
||||||
chai.expect(jsendResult.data.count).to.be.at.most(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
14
test/server/00-setup.test.js
Normal file
14
test/server/00-setup.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
// `meteor test` does NOT load server/main.js, so the app's
|
||||||
|
// server/00-collection2-init.js never runs and Mongo.Collection.prototype
|
||||||
|
// .attachSchema stays unpatched — every collection with a schema then throws at
|
||||||
|
// import time. This file mirrors that init. The `00-` prefix makes Meteor load
|
||||||
|
// it before the other test/server/*.test.js files (same-directory files load
|
||||||
|
// alphabetically), so attachSchema exists before any collection is imported.
|
||||||
|
// It intentionally defines no tests.
|
||||||
|
import 'meteor/aldeed:collection2/static.js';
|
||||||
|
// Force accounts-base to initialize so Meteor.users exists for method tests
|
||||||
|
// (server/main.js, which normally pulls in the accounts setup, is not loaded).
|
||||||
|
import 'meteor/accounts-base';
|
||||||
32
test/server/activeFiresUnion.test.js
Normal file
32
test/server/activeFiresUnion.test.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
|
||||||
|
|
||||||
|
const centerid = { type: 'Point', coordinates: [-5.905956029891968, 43.55727622097011] };
|
||||||
|
const shape = { type: 'Polygon', coordinates: [[[-5.9084, 43.5558], [-5.906, 43.5558], [-5.906, 43.5566], [-5.9036, 43.5566], [-5.9036, 43.5583], [-5.9061, 43.5583], [-5.9061, 43.558], [-5.9084, 43.558], [-5.9084, 43.5558]]] };
|
||||||
|
// `new Date(...)` (the old test used bare `Date(...)`, which yields a string and
|
||||||
|
// fails collection2 schema validation on a Date field).
|
||||||
|
const fireUnion = { centerid, shape, when: new Date('2018-05-02T16:11:04.617Z') };
|
||||||
|
|
||||||
|
describe('activeFireUnion insert', () => {
|
||||||
|
it('should insert fire union', async () => {
|
||||||
|
// Remove any leftover from a previous run
|
||||||
|
const alreadyInserted = await ActiveFiresUnion.findOneAsync({ centerid });
|
||||||
|
if (alreadyInserted) {
|
||||||
|
await ActiveFiresUnion.removeAsync(alreadyInserted._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertedId = await ActiveFiresUnion.insertAsync(fireUnion);
|
||||||
|
ActiveFiresUnion.schema.validate(fireUnion);
|
||||||
|
|
||||||
|
const inserted = await ActiveFiresUnion.findOneAsync(insertedId);
|
||||||
|
delete inserted._id;
|
||||||
|
expect(inserted).to.deep.equal(fireUnion);
|
||||||
|
|
||||||
|
await ActiveFiresUnion.removeAsync(insertedId);
|
||||||
|
expect(await ActiveFiresUnion.find({ _id: insertedId }).countAsync()).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
122
test/server/comments.test.js
Normal file
122
test/server/comments.test.js
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
/* 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 Comments from '/imports/api/Comments/Comments';
|
||||||
|
import getMediaFromContent from '/imports/api/Comments/mediaAnalyzers';
|
||||||
|
// `meteor test` skips server/main.js, so nothing registers the comment methods.
|
||||||
|
// Importing the module runs its Meteor.methods({...}) so the handlers exist.
|
||||||
|
import '/imports/api/Comments/server/methods';
|
||||||
|
|
||||||
|
// -- Embed detection (pure) --------------------------------------------------
|
||||||
|
describe('comment media analyzers', () => {
|
||||||
|
it('detects an image url', () => {
|
||||||
|
const media = getMediaFromContent('look at http://example.com/photo.jpg here');
|
||||||
|
expect(media.type).to.equal('image');
|
||||||
|
expect(media.content).to.match(/\.jpg$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects a youtube url and rewrites it to an embed url', () => {
|
||||||
|
const media = getMediaFromContent('see https://www.youtube.com/watch?v=abc123 now');
|
||||||
|
expect(media).to.deep.equal({ type: 'youtube', content: 'https://www.youtube.com/embed/abc123' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns {} for plain text', () => {
|
||||||
|
expect(getMediaFromContent('just some words with no media')).to.deep.equal({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- Comment methods (server, via method handlers) ---------------------------
|
||||||
|
// The methods gate on `this.userId`, so invoke the handlers with a fake
|
||||||
|
// invocation context instead of going through a real login.
|
||||||
|
describe('comment methods', () => {
|
||||||
|
const ref = 'fire-ref-under-test';
|
||||||
|
const owner = 'user-owner';
|
||||||
|
const other = 'user-other';
|
||||||
|
const handlers = Meteor.server.method_handlers;
|
||||||
|
|
||||||
|
const insert = (userId, content) => handlers['comments.insert'].apply({ userId }, [ref, content]);
|
||||||
|
const edit = (userId, id, content) => handlers['comments.edit'].apply({ userId }, [id, content]);
|
||||||
|
const remove = (userId, id) => handlers['comments.remove'].apply({ userId }, [id]);
|
||||||
|
const like = (userId, id) => handlers['comments.like'].apply({ userId }, [id]);
|
||||||
|
const dislike = (userId, id) => handlers['comments.dislike'].apply({ userId }, [id]);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await Comments.removeAsync({ referenceId: ref });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('insert creates an approved comment with detected media', async () => {
|
||||||
|
const id = await insert(owner, 'hi http://example.com/pic.png');
|
||||||
|
const c = await Comments.findOneAsync(id);
|
||||||
|
expect(c.userId).to.equal(owner);
|
||||||
|
expect(c.referenceId).to.equal(ref);
|
||||||
|
expect(c.status).to.equal('approved');
|
||||||
|
expect(c.media.type).to.equal('image');
|
||||||
|
expect(c.likes).to.deep.equal([]);
|
||||||
|
expect(c.dislikes).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('insert requires a logged-in user', async () => {
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await handlers['comments.insert'].apply({}, [ref, 'nope']);
|
||||||
|
} catch (e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
expect(threw).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('insert rejects an empty comment', async () => {
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await insert(owner, ' ');
|
||||||
|
} catch (e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
expect(threw).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('edit is allowed for the owner and denied for others', async () => {
|
||||||
|
const id = await insert(owner, 'original');
|
||||||
|
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await edit(other, id, 'hijacked');
|
||||||
|
} catch (e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
expect(threw).to.equal(true);
|
||||||
|
|
||||||
|
await edit(owner, id, 'edited');
|
||||||
|
expect((await Comments.findOneAsync(id)).content).to.equal('edited');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('like then dislike toggles the arrays', async () => {
|
||||||
|
const id = await insert(owner, 'toggle me');
|
||||||
|
|
||||||
|
await like(other, id);
|
||||||
|
expect((await Comments.findOneAsync(id)).likes).to.include(other);
|
||||||
|
|
||||||
|
await dislike(other, id);
|
||||||
|
const c = await Comments.findOneAsync(id);
|
||||||
|
expect(c.dislikes).to.include(other);
|
||||||
|
expect(c.likes).to.not.include(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remove is allowed for the owner only', async () => {
|
||||||
|
const id = await insert(owner, 'delete me');
|
||||||
|
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await remove(other, id);
|
||||||
|
} catch (e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
expect(threw).to.equal(true);
|
||||||
|
|
||||||
|
await remove(owner, id);
|
||||||
|
expect(await Comments.findOneAsync(id)).to.equal(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
test/server/email.test.js
Normal file
27
test/server/email.test.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { subjectTruncate } from '/imports/modules/server/send-email';
|
||||||
|
|
||||||
|
describe('check email utilities', () => {
|
||||||
|
it('small words', () => {
|
||||||
|
expect(subjectTruncate.apply('something', [50, true])).to.equal('something');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('big sentences', () => {
|
||||||
|
expect(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.'))
|
||||||
|
.to.equal('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('big sentences space break', () => {
|
||||||
|
expect(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.'))
|
||||||
|
.to.equal('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('big sentences no break', () => {
|
||||||
|
expect(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.', [70, false]))
|
||||||
|
.to.equal('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis...');
|
||||||
|
});
|
||||||
|
});
|
||||||
63
test/server/encode.test.js
Normal file
63
test/server/encode.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import urlEnc from '/imports/modules/url-encode';
|
||||||
|
|
||||||
|
// NOTE: the old suite also seeded ActiveFires via @cleverbeagle/seeder and
|
||||||
|
// round-tripped whole docs. Those cases were documented-broken (Date fields came
|
||||||
|
// back as strings) and depended on a dev-only seeder package that is no longer
|
||||||
|
// installed, so they were dropped. The crypto round-trip below is the real
|
||||||
|
// contract this module owes.
|
||||||
|
|
||||||
|
describe('url encoding', () => {
|
||||||
|
it('should encrypt and decrypt basic objects', async () => {
|
||||||
|
const obj = { lat: 2 };
|
||||||
|
const sealed = await urlEnc.encrypt(obj);
|
||||||
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
|
expect(unsealed).to.deep.equal(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encrypt and decrypt objects with date', async () => {
|
||||||
|
const obj = { lat: 40.234503, lon: -3.350386, when: (new Date()).toString() };
|
||||||
|
const sealed = await urlEnc.encrypt(obj);
|
||||||
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
|
expect(unsealed).to.deep.equal(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encrypt complete objects and produce url-safe, salted output', async () => {
|
||||||
|
const obj = {
|
||||||
|
ourid: { type: 'Point', coordinates: [24.813, 9.223] },
|
||||||
|
type: 'modis',
|
||||||
|
lat: 9.223,
|
||||||
|
lon: 24.813,
|
||||||
|
when: new Date('2017-12-13T00:00:00.000Z').toISOString(),
|
||||||
|
scan: 1,
|
||||||
|
track: 1,
|
||||||
|
satellite: 'A',
|
||||||
|
confidence: 'nominal',
|
||||||
|
version: '6.0NRT',
|
||||||
|
frp: 6.5,
|
||||||
|
daynight: null,
|
||||||
|
brightness: 305.9,
|
||||||
|
bright_t31: 292.6
|
||||||
|
};
|
||||||
|
|
||||||
|
const sealed = await urlEnc.encrypt(obj);
|
||||||
|
const sealed2 = await urlEnc.encrypt(obj);
|
||||||
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
|
|
||||||
|
expect(unsealed).to.deep.equal(obj);
|
||||||
|
expect(sealed).to.equal(encodeURI(sealed)); // already url-safe
|
||||||
|
expect(sealed).to.not.equal(sealed2); // salted: same input, different output
|
||||||
|
expect(sealed).to.not.include('/');
|
||||||
|
expect(sealed).to.not.include('%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decrypt a node-red produced blob without throwing', async () => {
|
||||||
|
const sealed = 'Fe26.2**7ae64199b871901d16a6ba031198c57b43b4a9231e15b851ff80014a8de230ca*RkB3_Uu_oJnTefyzB-Ocqg*2Z9rXYf--GxLJnYESHvdK5rC6lOMlSbMJ6YQyR2Mgpl57iBYopAHPWRLNTBgNkbUHFYa0IlEjdyeEz5VvLDtVvlSr1Sam-Cx8GUai4mharZO5-Wkze0dTr7M56WofSC9jpkv3kANrpsrit3HASE_E-5Z1JVYQVQqehw-VbSZTorrYj0GCJiAgXE5I0iY4boXRDYCv7fm_pzcuAbHlnNkKT1GbMFEPjyVViP4mVMRI5PDUhcWb0f62goMX4UnYLZXum4oYOIr84DG8AxqIdW2L94yslt0EZkD3yVhvKEGoauMpy6ry2illpSgaMaj4sU3AKWIjfAsJXvM6bHwbNh6G1_BJfCapuu3KijBBQPQMAnhYuwLAlY56WN-Y2efNIBJkp__kO6ak2nFqu8PufpxE-cv0uW7x6GWUtCpnFO2SeUJWVVMddUgJjLiM8Hkdl1v9huB0WdIvsoPEV0ZOwHZaC3jtdKFSO7Xe6LdqTXjXmdBMwtOv-HefyaB8LnEN6dAeKWwJuDu7g03QFTryDIiuwtpQ8mcLWTJGHcxoaluNhUESOBULkWSv_E1vOM1_fFv**1e41101b27c5da58bc11177448e91d88d7801bf911088a895844db0c542dfa7c*dPlOhtXJcyR47ezuL7CEgC2Z8d6C1asNOiqZmhD5TXY';
|
||||||
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
|
expect(unsealed).to.be.an('object');
|
||||||
|
});
|
||||||
|
});
|
||||||
32
test/server/file.test.js
Normal file
32
test/server/file.test.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
|
||||||
|
|
||||||
|
describe('get file of page', () => {
|
||||||
|
it('lang es retrieve es', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'es')).to.equal('pages/about-es.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lang en retrieve en', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'en')).to.equal('pages/about-en.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lang gl retrieve es', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'gl')).to.equal('pages/about-es.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lang eu retrieve es', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'eu')).to.equal('pages/about-es.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lang ca retrieve es', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'ca')).to.equal('pages/about-es.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lang es-PE retrieve es', () => {
|
||||||
|
expect(getFileNameOfLang('pages', 'about', 'md', 'es-PE')).to.equal('pages/about-es.md');
|
||||||
|
});
|
||||||
|
});
|
||||||
58
test/server/siteSettings.test.js
Normal file
58
test/server/siteSettings.test.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
|
import SiteSettingsTypes from '/imports/api/SiteSettings/SiteSettingsTypes';
|
||||||
|
|
||||||
|
const setting = {
|
||||||
|
name: 'site-test',
|
||||||
|
value: 'Some value',
|
||||||
|
description: 'Some description',
|
||||||
|
isPublic: true,
|
||||||
|
type: 'string'
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('site settings store', () => {
|
||||||
|
before(async () => {
|
||||||
|
await SiteSettings.createIndexAsync({ name: 1 }, { unique: true });
|
||||||
|
await SiteSettings.removeAsync({ name: setting.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get settingstypes', () => {
|
||||||
|
expect(SiteSettingsTypes.string.value.type).to.equal(String);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert settings', async () => {
|
||||||
|
const id = await SiteSettings.insertAsync(setting);
|
||||||
|
SiteSettings.getSchema(setting.type).validate(setting);
|
||||||
|
|
||||||
|
const inserted = await SiteSettings.findOneAsync(id);
|
||||||
|
delete inserted._id;
|
||||||
|
expect(inserted).to.deep.equal(setting);
|
||||||
|
|
||||||
|
await SiteSettings.removeAsync(id);
|
||||||
|
expect(await SiteSettings.find({ _id: id }).countAsync()).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be inserted twice', async () => {
|
||||||
|
const id = await SiteSettings.insertAsync(setting);
|
||||||
|
// The unique index on `name` must reject the duplicate.
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await SiteSettings.insertAsync(setting);
|
||||||
|
} catch (e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
expect(threw).to.equal(true);
|
||||||
|
expect(await SiteSettings.find(id).countAsync()).to.equal(1);
|
||||||
|
await SiteSettings.removeAsync(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail validation', () => {
|
||||||
|
expect(() => {
|
||||||
|
SiteSettings.getSchema('boolean').validate(setting);
|
||||||
|
}).to.throw('Value must be of type Boolean');
|
||||||
|
});
|
||||||
|
});
|
||||||
51
test/server/tweets.test.js
Normal file
51
test/server/tweets.test.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { composeIberiaTweet } from '/imports/api/ActiveFires/server/tweetFiresInZone';
|
||||||
|
|
||||||
|
const tr = (stats) => {
|
||||||
|
const keys = Object.keys(stats);
|
||||||
|
const newStats = [];
|
||||||
|
keys.map((key) => {
|
||||||
|
newStats[key] = { count: stats[key] };
|
||||||
|
});
|
||||||
|
return newStats;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('compose tweets of fires', () => {
|
||||||
|
it('No fires', () => {
|
||||||
|
expect(composeIberiaTweet(0, { })).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a fire in a zone', () => {
|
||||||
|
expect(composeIberiaTweet(1, tr({ Asturias: 1 }))).to.equal('Un #fuego activo en #Asturias');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('some fires in a zone', () => {
|
||||||
|
expect(composeIberiaTweet(4, tr({ Asturias: 4 }))).to.equal('4 #fuegos activos en #Asturias');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('some fires in two zones', () => {
|
||||||
|
expect(composeIberiaTweet(5, tr({ Asturias: 1, Madrid: 4 }))).to.equal('Un #fuego activo en #Asturias y otros 4 en #Madrid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('some fires in some zones', () => {
|
||||||
|
expect(composeIberiaTweet(8, tr({ Catalunya: 3, Asturias: 1, Madrid: 4 }))).to.equal('3 #fuegos activos en #Catalunya, otro en #Asturias y otros 4 en #Madrid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other fires in some zones', () => {
|
||||||
|
expect(composeIberiaTweet(6, tr({ Catalunya: 1, Asturias: 1, Madrid: 4 }))).to.equal('Un #fuego activo en #Catalunya, otro en #Asturias y otros 4 en #Madrid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other fires in two zones', () => {
|
||||||
|
expect(composeIberiaTweet(2, tr({ Catalunya: 1, Asturias: 1 }))).to.equal('Un #fuego activo en #Catalunya y otro en #Asturias');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('many fires in many zones', () => {
|
||||||
|
expect(composeIberiaTweet(11, tr({
|
||||||
|
Cataluña: 3, Cantabria: 1, Andalucia: 4, Galicia: 1, Madrid: 2
|
||||||
|
}))).to.equal('3 #fuegos activos en #Cataluña, otro en #Cantabria, otros 4 en #Andalucia, otro en #Galicia y otros 2 en #Madrid');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
|
||||||
import SiteSettingsTypes from '/imports/api/SiteSettings/SiteSettingsTypes';
|
|
||||||
import { resetDatabase } from 'meteor/xolvio:cleaner';
|
|
||||||
|
|
||||||
resetDatabase('siteSettings');
|
|
||||||
SiteSettings._ensureIndex({ name: 1 }, { unique: 1 });
|
|
||||||
|
|
||||||
const setting = {
|
|
||||||
name: 'site-test',
|
|
||||||
value: 'Some value',
|
|
||||||
description: 'Some description',
|
|
||||||
isPublic: true,
|
|
||||||
type: 'string'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('site settings store', () => {
|
|
||||||
it('should get settingstypes', () => {
|
|
||||||
chai.expect(SiteSettingsTypes.string.value.type).equal(String);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert settings', () => {
|
|
||||||
// Remove previous test register
|
|
||||||
const alreadyInserted = SiteSettings.findOne({ name: 'site-test' });
|
|
||||||
if (alreadyInserted) {
|
|
||||||
SiteSettings.remove(alreadyInserted._id);
|
|
||||||
}
|
|
||||||
const id = SiteSettings.insert(setting);
|
|
||||||
SiteSettings.getSchema(setting.type).validate(setting);
|
|
||||||
|
|
||||||
const inserted = SiteSettings.findOne(id);
|
|
||||||
delete inserted._id;
|
|
||||||
chai.expect(setting).to.deep.equal(inserted);
|
|
||||||
SiteSettings.remove(id);
|
|
||||||
chai.expect(SiteSettings.find({ _id: inserted }).count()).equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not be inserted twice', () => {
|
|
||||||
const id = SiteSettings.insert(setting);
|
|
||||||
// If this fails manual check that the setting it's not already in the db.
|
|
||||||
chai.expect(() => {
|
|
||||||
SiteSettings.insert(setting);
|
|
||||||
}).to.throw();
|
|
||||||
const inserted = SiteSettings.find(id).count();
|
|
||||||
chai.expect(inserted).equal(1);
|
|
||||||
SiteSettings.remove(id);
|
|
||||||
chai.expect(SiteSettings.find({ _id: inserted }).count()).equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail validation', () => {
|
|
||||||
chai.expect(() => {
|
|
||||||
SiteSettings.getSchema('boolean').validate(setting);
|
|
||||||
}).to.throw('Value must be of type Boolean');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
/* eslint-env mocha */
|
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { chai } from 'meteor/practicalmeteor:chai';
|
|
||||||
import { composeIberiaTweet } from '/imports/api/ActiveFires/server/tweetFiresInZone';
|
|
||||||
|
|
||||||
const tr = (stats) => {
|
|
||||||
const keys = Object.keys(stats);
|
|
||||||
const newStats = [];
|
|
||||||
keys.map((key) => {
|
|
||||||
newStats[key] = { count: stats[key] };
|
|
||||||
});
|
|
||||||
return newStats;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('compose tweets of fires', () => {
|
|
||||||
it('No fires', async () => {
|
|
||||||
chai.expect('').to.deep.equal(composeIberiaTweet(0, { }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('a fire in a zone', async () => {
|
|
||||||
chai.expect('Un #fuego activo en #Asturias').to.deep.equal(composeIberiaTweet(1, tr({ Asturias: 1 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('some fires in a zone', async () => {
|
|
||||||
chai.expect('4 #fuegos activos en #Asturias').to.deep.equal(composeIberiaTweet(4, tr({ Asturias: 4 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('some fires in two zones', async () => {
|
|
||||||
chai.expect('Un #fuego activo en #Asturias y otros 4 en #Madrid').to.deep.equal(composeIberiaTweet(5, tr({ Asturias: 1, Madrid: 4 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('some fires in some zones', async () => {
|
|
||||||
chai.expect('3 #fuegos activos en #Catalunya, otro en #Asturias y otros 4 en #Madrid').to.deep.equal(composeIberiaTweet(8, tr({ Catalunya: 3, Asturias: 1, Madrid: 4 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('other fires in some zones', async () => {
|
|
||||||
chai.expect('Un #fuego activo en #Catalunya, otro en #Asturias y otros 4 en #Madrid').to.deep.equal(composeIberiaTweet(6, tr({ Catalunya: 1, Asturias: 1, Madrid: 4 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('other fires in two zones', async () => {
|
|
||||||
chai.expect('Un #fuego activo en #Catalunya y otro en #Asturias').to.deep.equal(composeIberiaTweet(2, tr({ Catalunya: 1, Asturias: 1 })));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('many fires in many zones', async () => {
|
|
||||||
chai.expect('3 #fuegos activos en #Cataluña, otro en #Cantabria, otros 4 en #Andalucia, otro en #Galicia y otros 2 en #Madrid').to.deep.equal(composeIberiaTweet(11, tr({
|
|
||||||
Cataluña: 3, Cantabria: 1, Andalucia: 4, Galicia: 1, Madrid: 2
|
|
||||||
})));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue