smoke: REST API regression harness + baseline snapshots (Meteor 1.6.1.1)
Adds a standalone Node smoke test (smoke/) that exercises every REST endpoint consumed by the Flutter app against a seeded local dev server and compares responses to committed snapshots. This is the safety net for the Meteor upgrade: it must stay byte-identical after every escala. - smoke/seed.js: deterministic Mongo seed (fixed ObjectIds, geo indexes) - smoke/run.js: calls endpoints, normalizes volatile fields, diffs snapshots - smoke/smoke.sh: seed + run wrapper - smoke/snapshots/: baseline captured on Meteor 1.6.1.1 - IPGeocoder.js: degrade gracefully when MaxMind DB absent (dev boot) - settings-development.json: dev-only internalApiToken to enable the REST API - .meteorignore: exclude smoke/ from the Meteor server build
This commit is contained in:
parent
39415808bf
commit
296dab4f38
18 changed files with 539 additions and 4 deletions
|
|
@ -1,3 +1,5 @@
|
|||
# Don't reload meteor server when we modify or do test
|
||||
cucumber
|
||||
output.json
|
||||
# REST smoke-test harness runs standalone (Node), never as Meteor server code
|
||||
smoke
|
||||
|
|
|
|||
|
|
@ -21,11 +21,16 @@ function isPrivateIP(ip) {
|
|||
const dbpath = '/usr/local/share/maxmind-geolite2/GeoLite2-City.mmdb';
|
||||
// const dbpath = `${process.env.PWD}/private/GeoLite2-City.mmdb`;
|
||||
|
||||
if (!fs.existsSync(dbpath)) {
|
||||
let IPGeocoder;
|
||||
if (fs.existsSync(dbpath)) {
|
||||
IPGeocoder = maxmind.openSync(dbpath);
|
||||
} else {
|
||||
// In production the DB is provisioned via cron; in local dev it may be
|
||||
// absent. Degrade gracefully instead of crashing at boot: localize() below
|
||||
// already falls back to a default location when no geo data is available.
|
||||
console.error(`Maxmind db not found ${dbpath}, download via cron with https://www.npmjs.com/package/maxmind-geolite2-mirror`);
|
||||
IPGeocoder = { get() { return null; } };
|
||||
}
|
||||
|
||||
const IPGeocoder = maxmind.openSync(dbpath);
|
||||
export default IPGeocoder;
|
||||
|
||||
// Warning: Meteor cannot access to this.connection with arrow functions
|
||||
|
|
@ -47,7 +52,7 @@ export function localize() {
|
|||
// http://dev.maxmind.com/geoip/geoip2/geolite2/
|
||||
const geo = IPGeocoder.get(clientIP);
|
||||
// console.warn(geo);
|
||||
if (geo.location && geo.location.latitude && geo.location.longitude) {
|
||||
if (geo && geo.location && geo.location.latitude && geo.location.longitude) {
|
||||
return geo;
|
||||
}
|
||||
// geoIP fallback, Madrid
|
||||
|
|
|
|||
73
smoke/README.md
Normal file
73
smoke/README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# REST smoke test
|
||||
|
||||
Regression harness for the REST API in `imports/api/Rest/Rest.js` — the API the
|
||||
Flutter app (`fires_flutter/lib/models/fires_api.dart`) depends on.
|
||||
|
||||
It calls every Flutter-consumed endpoint against a locally running dev server
|
||||
with deterministic seed data, normalizes the intrinsically volatile fields
|
||||
(uptime, generated ids, insert timestamps) and compares the responses to
|
||||
committed snapshots in `smoke/snapshots/`.
|
||||
|
||||
**It must stay green after every Meteor upgrade escala. The snapshots are the
|
||||
contract: any drift is treated as an upgrade bug.**
|
||||
|
||||
## Reproducible local startup
|
||||
|
||||
1. **MongoDB** (prod parity = 3.2; a dev-only instance in Docker):
|
||||
|
||||
```bash
|
||||
docker run -d --name tcef-mongo32 -p 27018:27017 mongo:3.2
|
||||
```
|
||||
|
||||
2. **Meteor dev server** (from the repo root):
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/.meteor:$PATH"
|
||||
export MONGO_URL="mongodb://localhost:27018/fuegos"
|
||||
meteor --settings settings-development.json --port 3100
|
||||
```
|
||||
|
||||
`settings-development.json` carries a dev-only `private.internalApiToken`
|
||||
(`dev-smoke-token`) which is what enables the REST API. Without it the API
|
||||
routes are not even registered (see `Rest.js`).
|
||||
|
||||
> Note: `IPGeocoder.js` degrades gracefully when the MaxMind GeoLite2 DB is
|
||||
> absent (it is provisioned by cron in production) so the server boots in a
|
||||
> clean checkout.
|
||||
|
||||
## Running the smoke test
|
||||
|
||||
```bash
|
||||
./smoke/smoke.sh # seed + compare against baseline snapshots
|
||||
./smoke/smoke.sh --update # seed + regenerate the baseline snapshots
|
||||
```
|
||||
|
||||
Env overrides: `MONGO_CONTAINER` (default `tcef-mongo32`), `MONGO_DB`
|
||||
(`fuegos`), `BASE_URL` (`http://localhost:3100`).
|
||||
|
||||
## What is covered
|
||||
|
||||
| Snapshot | Endpoint | Flutter call |
|
||||
|---|---|---|
|
||||
| `status_uptime` | `GET status/uptime` | — |
|
||||
| `status_active_fires_count` | `GET status/active-fires-count` | — |
|
||||
| `status_last_fire_check` | `GET status/last-fire-check` | — |
|
||||
| `status_last_fire_detected` | `GET status/last-fire-detected` | — |
|
||||
| `status_subs_public_union` | `GET status/subs-public-union/:token` | `getMonitoredAreas` |
|
||||
| `fires_in` | `GET fires-in/:token/:lat/:lng/:km` | — |
|
||||
| `fires_in_full` | `GET fires-in-full/:token/:lat/:lng/:km` | `getFiresInLocation` |
|
||||
| `mobile_users_post` | `POST mobile/users` | `createUser` |
|
||||
| `mobile_subscriptions_post` | `POST mobile/subscriptions` | `subscribe` |
|
||||
| `mobile_subscriptions_all` | `GET mobile/subscriptions/all/:token/:mobileToken` | `fetchYourLocations` |
|
||||
| `mobile_subscriptions_delete` | `DELETE mobile/subscriptions/:token/:mobileToken/:subsId` | `unsubscribe` |
|
||||
| `mobile_falsepositive_post` | `POST mobile/falsepositive` | `markFalsePositive` |
|
||||
|
||||
## Normalized (volatile) fields
|
||||
|
||||
Compared structurally but blanked to a placeholder because they cannot be
|
||||
deterministic: `status/uptime` → `ms`; `mobile/users` → `userId`, `username`,
|
||||
`upsertResult.insertedId`; `mobile/subscriptions/all` → `owner`, `createdAt`,
|
||||
`updatedAt`; `mobile/falsepositive` → `upsert.insertedId`.
|
||||
|
||||
Everything else (ids of seeded docs, geometries, counts, jsend envelope, field
|
||||
names and types) is asserted byte-for-byte.
|
||||
172
smoke/run.js
Executable file
172
smoke/run.js
Executable file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env node
|
||||
/*
|
||||
* REST smoke test for the Flutter-consumed API of todos-contra-el-fuego-web.
|
||||
*
|
||||
* Calls every endpoint the Flutter app (fires_flutter/lib/models/fires_api.dart)
|
||||
* depends on, normalizes the intrinsically volatile fields (uptime, generated
|
||||
* ids, insert timestamps), and compares the result against committed snapshots.
|
||||
*
|
||||
* The responses MUST stay identical across every Meteor upgrade escala — the
|
||||
* app parses them field by field.
|
||||
*
|
||||
* Usage:
|
||||
* node smoke/run.js --update # (re)write baseline snapshots
|
||||
* node smoke/run.js # compare against baseline, exit 1 on drift
|
||||
*
|
||||
* Env:
|
||||
* BASE_URL default http://localhost:3100
|
||||
* SETTINGS default settings-development.json (token + ironPassword)
|
||||
*
|
||||
* Requires the dev server running and the DB seeded (smoke/seed.sh does both).
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const SNAP_DIR = path.join(__dirname, 'snapshots');
|
||||
const BASE = (process.env.BASE_URL || 'http://localhost:3100').replace(/\/$/, '');
|
||||
const API = `${BASE}/api/v1`;
|
||||
const UPDATE = process.argv.includes('--update');
|
||||
|
||||
const settings = JSON.parse(fs.readFileSync(path.join(ROOT, process.env.SETTINGS || 'settings-development.json'), 'utf8'));
|
||||
const TOKEN = settings.private.internalApiToken;
|
||||
const IRON_PASSWORD = settings.private.ironPassword;
|
||||
|
||||
// Fixed inputs — must match smoke/seed.js
|
||||
const MOBILE_TOKEN = 'smoke-mobile-token';
|
||||
const SUBS_ID = 'd0000000000000000000000d';
|
||||
const LAT = 40.41;
|
||||
const LON = -3.70;
|
||||
|
||||
// ---------- helpers ----------
|
||||
function stableStringify(v) {
|
||||
return JSON.stringify(sortKeys(v), null, 2);
|
||||
}
|
||||
function sortKeys(v) {
|
||||
if (Array.isArray(v)) return v.map(sortKeys);
|
||||
if (v && typeof v === 'object') {
|
||||
return Object.keys(v).sort().reduce((acc, k) => { acc[k] = sortKeys(v[k]); return acc; }, {});
|
||||
}
|
||||
return v;
|
||||
}
|
||||
async function req(method, url, body) {
|
||||
const opts = { method, headers: {} };
|
||||
if (body !== undefined) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); }
|
||||
const res = await fetch(url, opts);
|
||||
const text = await res.text();
|
||||
let json;
|
||||
try { json = JSON.parse(text); } catch (e) { json = { __nonJson: text }; }
|
||||
return { status: res.status, json };
|
||||
}
|
||||
// Replace volatile fields (by key name) with a stable placeholder, recursively.
|
||||
function scrub(obj, keys) {
|
||||
if (Array.isArray(obj)) return obj.map(o => scrub(o, keys));
|
||||
if (obj && typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
out[k] = keys.includes(k) ? `<${k}>` : scrub(obj[k], keys);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// ---------- iron seal for mobile/falsepositive ----------
|
||||
async function sealFire() {
|
||||
const Iron = require('iron');
|
||||
const fire = {
|
||||
ourid: { type: 'Point', coordinates: [LON, LAT] },
|
||||
lat: LAT, lon: LON, type: 'viirs',
|
||||
when: '2024-01-01T00:00:00.000Z',
|
||||
scan: 0.5, track: 0.4, address: 'Madrid, Spain'
|
||||
};
|
||||
// iron v5 seal returns a promise
|
||||
return Iron.seal(fire, IRON_PASSWORD, Iron.defaults);
|
||||
}
|
||||
|
||||
// ---------- test sequence ----------
|
||||
async function collect() {
|
||||
const snaps = {};
|
||||
|
||||
snaps['status_uptime'] = scrub((await req('GET', `${API}/status/uptime`)).json, ['ms']);
|
||||
snaps['status_active_fires_count'] = (await req('GET', `${API}/status/active-fires-count`)).json;
|
||||
snaps['status_last_fire_check'] = (await req('GET', `${API}/status/last-fire-check`)).json;
|
||||
snaps['status_last_fire_detected'] = (await req('GET', `${API}/status/last-fire-detected`)).json;
|
||||
snaps['status_subs_public_union'] = (await req('GET', `${API}/status/subs-public-union/${TOKEN}`)).json;
|
||||
|
||||
// Read-only fires query BEFORE any false-positive is inserted.
|
||||
snaps['fires_in_full'] = (await req('GET', `${API}/fires-in-full/${TOKEN}/${LAT}/${LON}/100`)).json;
|
||||
snaps['fires_in'] = (await req('GET', `${API}/fires-in/${TOKEN}/${LAT}/${LON}/100`)).json;
|
||||
|
||||
// Create mobile user
|
||||
const users = await req('POST', `${API}/mobile/users`, { token: TOKEN, mobileToken: MOBILE_TOKEN, lang: 'es' });
|
||||
snaps['mobile_users_post'] = scrub(users.json, ['userId', 'username', 'insertedId']);
|
||||
|
||||
// Subscribe
|
||||
const sub = await req('POST', `${API}/mobile/subscriptions`, {
|
||||
token: TOKEN, mobileToken: MOBILE_TOKEN, id: SUBS_ID, lat: LAT, lon: LON, distance: 40
|
||||
});
|
||||
snaps['mobile_subscriptions_post'] = sub.json;
|
||||
|
||||
// List subscriptions
|
||||
const list = await req('GET', `${API}/mobile/subscriptions/all/${TOKEN}/${MOBILE_TOKEN}`);
|
||||
snaps['mobile_subscriptions_all'] = scrub(list.json, ['owner', 'createdAt', 'updatedAt']);
|
||||
|
||||
// Delete the subscription
|
||||
const del = await req('DELETE', `${API}/mobile/subscriptions/${TOKEN}/${MOBILE_TOKEN}/${SUBS_ID}`);
|
||||
snaps['mobile_subscriptions_delete'] = del.json;
|
||||
|
||||
// False positive (LAST — mutates falsePositives collection)
|
||||
const sealed = await sealFire();
|
||||
const fp = await req('POST', `${API}/mobile/falsepositive`, {
|
||||
token: TOKEN, mobileToken: MOBILE_TOKEN, sealed, type: 'falsealarm'
|
||||
});
|
||||
snaps['mobile_falsepositive_post'] = scrub(fp.json, ['insertedId']);
|
||||
|
||||
return snaps;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!TOKEN) { console.error('No internalApiToken in settings'); process.exit(2); }
|
||||
fs.mkdirSync(SNAP_DIR, { recursive: true });
|
||||
const snaps = await collect();
|
||||
|
||||
let fail = 0;
|
||||
for (const [name, value] of Object.entries(snaps)) {
|
||||
const file = path.join(SNAP_DIR, `${name}.json`);
|
||||
const got = stableStringify(value);
|
||||
if (UPDATE) {
|
||||
fs.writeFileSync(file, `${got}\n`);
|
||||
console.log(` wrote ${name}`);
|
||||
continue;
|
||||
}
|
||||
if (!fs.existsSync(file)) { console.error(` MISSING baseline: ${name}`); fail++; continue; }
|
||||
const want = fs.readFileSync(file, 'utf8').trimEnd();
|
||||
if (want === got.trimEnd()) {
|
||||
console.log(` PASS ${name}`);
|
||||
} else {
|
||||
fail++;
|
||||
console.error(` FAIL ${name}`);
|
||||
console.error(diff(want, got));
|
||||
}
|
||||
}
|
||||
if (UPDATE) { console.log('baseline updated'); return; }
|
||||
if (fail) { console.error(`\n${fail} snapshot(s) drifted`); process.exit(1); }
|
||||
console.log('\nAll REST snapshots identical ✔');
|
||||
}
|
||||
|
||||
function diff(a, b) {
|
||||
const al = a.split('\n');
|
||||
const bl = b.split('\n');
|
||||
const out = [];
|
||||
const n = Math.max(al.length, bl.length);
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (al[i] !== bl[i]) {
|
||||
if (al[i] !== undefined) out.push(` - ${al[i]}`);
|
||||
if (bl[i] !== undefined) out.push(` + ${bl[i]}`);
|
||||
}
|
||||
}
|
||||
return out.slice(0, 40).join('\n');
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error(e); process.exit(2); });
|
||||
96
smoke/seed.js
Normal file
96
smoke/seed.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Deterministic seed for the REST smoke test.
|
||||
* Run against the local Mongo used by the dev Meteor server, e.g.:
|
||||
* docker exec -i tcef-mongo32 mongo fuegos < smoke/seed.js
|
||||
*
|
||||
* It resets the collections the REST API reads/writes to a fixed, known state
|
||||
* so that endpoint responses are reproducible across Meteor upgrade escalas.
|
||||
* Only test-owned documents are touched.
|
||||
*/
|
||||
|
||||
// --- Fixed identifiers (24-hex ObjectIds) -------------------------------
|
||||
var FIRE_A = ObjectId('a0000000000000000000000a');
|
||||
var FIRE_B = ObjectId('b0000000000000000000000b');
|
||||
var ARCHIVE_FIRE = ObjectId('c0000000000000000000000c');
|
||||
var MOBILE_TOKEN = 'smoke-mobile-token';
|
||||
|
||||
// Madrid-ish coordinates
|
||||
var LAT = 40.41;
|
||||
var LON = -3.70;
|
||||
|
||||
var WHEN = ISODate('2024-01-01T00:00:00.000Z');
|
||||
var WHEN_B = ISODate('2024-01-02T00:00:00.000Z'); // later, so last-fire-detected is deterministic
|
||||
var FIXED = ISODate('2024-01-01T00:00:00.000Z');
|
||||
|
||||
// --- activefires --------------------------------------------------------
|
||||
db.activefires.deleteMany({});
|
||||
db.activefires.insertMany([
|
||||
{
|
||||
_id: FIRE_A,
|
||||
ourid: { type: 'Point', coordinates: [LON, LAT] },
|
||||
lat: LAT, lon: LON, type: 'viirs', when: WHEN,
|
||||
scan: 0.5, track: 0.4, acq_date: '2024-01-01', acq_time: '00:00',
|
||||
satellite: 'N', confidence: 50, version: '1.0NRT', frp: 4.3,
|
||||
daynight: 'N', bright_ti4: 330.2, bright_ti5: 291,
|
||||
createdAt: FIXED, updatedAt: FIXED
|
||||
},
|
||||
{
|
||||
_id: FIRE_B,
|
||||
ourid: { type: 'Point', coordinates: [LON + 0.02, LAT + 0.02] },
|
||||
lat: LAT + 0.02, lon: LON + 0.02, type: 'viirs', when: WHEN_B,
|
||||
scan: 0.5, track: 0.4, acq_date: '2024-01-01', acq_time: '00:05',
|
||||
satellite: 'N', confidence: 60, version: '1.0NRT', frp: 6.1,
|
||||
daynight: 'N', bright_ti4: 331.0, bright_ti5: 292,
|
||||
createdAt: FIXED, updatedAt: FIXED
|
||||
}
|
||||
]);
|
||||
db.activefires.createIndex({ ourid: '2dsphere' });
|
||||
|
||||
// --- fires (archive) — must match the sealed fire used by mobile/falsepositive
|
||||
db.fires.deleteMany({});
|
||||
db.fires.insertOne({
|
||||
_id: ARCHIVE_FIRE,
|
||||
ourid: { type: 'Point', coordinates: [LON, LAT] },
|
||||
lat: LAT, lon: LON, type: 'viirs', when: WHEN,
|
||||
scan: 0.5, track: 0.4, address: 'Madrid, Spain',
|
||||
createdAt: FIXED, updatedAt: FIXED
|
||||
});
|
||||
db.fires.createIndex({ ourid: '2dsphere' });
|
||||
|
||||
// --- falsePositives / industries: empty so "real fire" counting is stable
|
||||
db.falsePositives.deleteMany({});
|
||||
db.industries.deleteMany({});
|
||||
db.industries.createIndex({ geo: '2dsphere' });
|
||||
|
||||
// --- subscriptions: clean; index for geo queries
|
||||
db.subscriptions.deleteMany({});
|
||||
db.subscriptions.createIndex({ geo: '2dsphere' });
|
||||
|
||||
// --- siteSettings consumed by status endpoints --------------------------
|
||||
db.siteSettings.deleteMany({ name: { $in: ['last-fire-check', 'subs-public-union', 'subs-public-union-bounds'] } });
|
||||
db.siteSettings.insertMany([
|
||||
{ _id: ObjectId('51e00000000000000000000c'), name: 'last-fire-check', value: FIXED, createdAt: FIXED, updatedAt: FIXED },
|
||||
{
|
||||
_id: ObjectId('51e0000000000000000000a1'),
|
||||
name: 'subs-public-union',
|
||||
value: JSON.stringify({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'MultiPolygon',
|
||||
coordinates: [[[[LON - 1, LAT - 1], [LON + 1, LAT - 1], [LON + 1, LAT + 1], [LON - 1, LAT + 1], [LON - 1, LAT - 1]]]]
|
||||
}
|
||||
}),
|
||||
createdAt: FIXED, updatedAt: FIXED
|
||||
},
|
||||
{
|
||||
_id: ObjectId('51e0000000000000000000b2'),
|
||||
name: 'subs-public-union-bounds',
|
||||
value: JSON.stringify({ ne: { lat: LAT + 1, lon: LON + 1 }, sw: { lat: LAT - 1, lon: LON - 1 } }),
|
||||
createdAt: FIXED, updatedAt: FIXED
|
||||
}
|
||||
]);
|
||||
|
||||
// --- users: remove any previous smoke user so mobile/users creates fresh
|
||||
db.users.deleteMany({ fireBaseToken: MOBILE_TOKEN });
|
||||
|
||||
print('seed OK: activefires=' + db.activefires.count() + ' fires=' + db.fires.count() + ' siteSettings=' + db.siteSettings.count({ name: /fire|union/ }));
|
||||
25
smoke/smoke.sh
Executable file
25
smoke/smoke.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env bash
|
||||
# Seed the dev Mongo deterministically and run the REST smoke test.
|
||||
#
|
||||
# smoke/smoke.sh # compare against committed baseline snapshots
|
||||
# smoke/smoke.sh --update # (re)generate the baseline snapshots
|
||||
#
|
||||
# Env overrides:
|
||||
# MONGO_CONTAINER docker container running the dev Mongo (default tcef-mongo32)
|
||||
# MONGO_DB database name (default fuegos)
|
||||
# BASE_URL dev server URL (default http://localhost:3100)
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
MONGO_CONTAINER="${MONGO_CONTAINER:-tcef-mongo32}"
|
||||
MONGO_DB="${MONGO_DB:-fuegos}"
|
||||
|
||||
echo "== Seeding ${MONGO_DB} in ${MONGO_CONTAINER} =="
|
||||
# Copy the script in and run it as a whole file: the mongo shell evaluates a
|
||||
# file argument atomically (piping via stdin is line-by-line and breaks on
|
||||
# multi-line comments / var scope).
|
||||
docker cp smoke/seed.js "${MONGO_CONTAINER}:/tmp/tcef-seed.js"
|
||||
docker exec "${MONGO_CONTAINER}" mongo "${MONGO_DB}" /tmp/tcef-seed.js | tail -1
|
||||
|
||||
echo "== Running REST smoke test against ${BASE_URL:-http://localhost:3100} =="
|
||||
node smoke/run.js "$@"
|
||||
4
smoke/snapshots/fires_in.json
Normal file
4
smoke/snapshots/fires_in.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"real": 2,
|
||||
"total": 2
|
||||
}
|
||||
30
smoke/snapshots/fires_in_full.json
Normal file
30
smoke/snapshots/fires_in_full.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"falsePos": [],
|
||||
"fires": [
|
||||
{
|
||||
"_id": {
|
||||
"_str": "a0000000000000000000000a"
|
||||
},
|
||||
"lat": 40.41,
|
||||
"lon": -3.7,
|
||||
"scan": 0.5,
|
||||
"track": 0.4,
|
||||
"type": "viirs",
|
||||
"when": "2024-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"_str": "b0000000000000000000000b"
|
||||
},
|
||||
"lat": 40.43,
|
||||
"lon": -3.68,
|
||||
"scan": 0.5,
|
||||
"track": 0.4,
|
||||
"type": "viirs",
|
||||
"when": "2024-01-02T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"industries": [],
|
||||
"real": 2,
|
||||
"total": 2
|
||||
}
|
||||
9
smoke/snapshots/mobile_falsepositive_post.json
Normal file
9
smoke/snapshots/mobile_falsepositive_post.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"data": {
|
||||
"upsert": {
|
||||
"insertedId": "<insertedId>",
|
||||
"numberAffected": 1
|
||||
}
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
29
smoke/snapshots/mobile_subscriptions_all.json
Normal file
29
smoke/snapshots/mobile_subscriptions_all.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"data": {
|
||||
"count": 1,
|
||||
"subscriptions": [
|
||||
{
|
||||
"_id": {
|
||||
"_str": "d0000000000000000000000d"
|
||||
},
|
||||
"createdAt": "<createdAt>",
|
||||
"distance": 40,
|
||||
"geo": {
|
||||
"coordinates": [
|
||||
-3.7,
|
||||
40.41
|
||||
],
|
||||
"type": "Point"
|
||||
},
|
||||
"location": {
|
||||
"lat": 40.41,
|
||||
"lon": -3.7
|
||||
},
|
||||
"owner": "<owner>",
|
||||
"type": "mobile",
|
||||
"updatedAt": "<updatedAt>"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
4
smoke/snapshots/mobile_subscriptions_delete.json
Normal file
4
smoke/snapshots/mobile_subscriptions_delete.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"data": {},
|
||||
"status": "success"
|
||||
}
|
||||
6
smoke/snapshots/mobile_subscriptions_post.json
Normal file
6
smoke/snapshots/mobile_subscriptions_post.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"data": {
|
||||
"subsId": "d0000000000000000000000d"
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
13
smoke/snapshots/mobile_users_post.json
Normal file
13
smoke/snapshots/mobile_users_post.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"data": {
|
||||
"lang": "es",
|
||||
"mobileToken": "smoke-mobile-token",
|
||||
"upsertResult": {
|
||||
"insertedId": "<insertedId>",
|
||||
"numberAffected": 1
|
||||
},
|
||||
"userId": "<userId>",
|
||||
"username": "<username>"
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
3
smoke/snapshots/status_active_fires_count.json
Normal file
3
smoke/snapshots/status_active_fires_count.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"total": 2
|
||||
}
|
||||
9
smoke/snapshots/status_last_fire_check.json
Normal file
9
smoke/snapshots/status_last_fire_check.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"_id": {
|
||||
"_str": "51e00000000000000000000c"
|
||||
},
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"name": "last-fire-check",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z",
|
||||
"value": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
29
smoke/snapshots/status_last_fire_detected.json
Normal file
29
smoke/snapshots/status_last_fire_detected.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"_id": {
|
||||
"_str": "b0000000000000000000000b"
|
||||
},
|
||||
"acq_date": "2024-01-01",
|
||||
"acq_time": "00:05",
|
||||
"bright_ti4": 331,
|
||||
"bright_ti5": 292,
|
||||
"confidence": 60,
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"daynight": "N",
|
||||
"frp": 6.1,
|
||||
"lat": 40.43,
|
||||
"lon": -3.68,
|
||||
"ourid": {
|
||||
"coordinates": [
|
||||
-3.68,
|
||||
40.43
|
||||
],
|
||||
"type": "Point"
|
||||
},
|
||||
"satellite": "N",
|
||||
"scan": 0.5,
|
||||
"track": 0.4,
|
||||
"type": "viirs",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z",
|
||||
"version": "1.0NRT",
|
||||
"when": "2024-01-02T00:00:00.000Z"
|
||||
}
|
||||
23
smoke/snapshots/status_subs_public_union.json
Normal file
23
smoke/snapshots/status_subs_public_union.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"data": {
|
||||
"bounds": {
|
||||
"_id": {
|
||||
"_str": "51e0000000000000000000b2"
|
||||
},
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"name": "subs-public-union-bounds",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z",
|
||||
"value": "{\"ne\":{\"lat\":41.41,\"lon\":-2.7},\"sw\":{\"lat\":39.41,\"lon\":-4.7}}"
|
||||
},
|
||||
"union": {
|
||||
"_id": {
|
||||
"_str": "51e0000000000000000000a1"
|
||||
},
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"name": "subs-public-union",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z",
|
||||
"value": "{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-4.7,39.41],[-2.7,39.41],[-2.7,41.41],[-4.7,41.41],[-4.7,39.41]]]]}}"
|
||||
}
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
3
smoke/snapshots/status_uptime.json
Normal file
3
smoke/snapshots/status_uptime.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ms": "<ms>"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue