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.
122 lines
4.1 KiB
JavaScript
122 lines
4.1 KiB
JavaScript
/* eslint-env mocha */
|
|
/* eslint-disable func-names, prefer-arrow-callback */
|
|
/* eslint-disable import/no-absolute-path */
|
|
|
|
import { expect } from 'chai';
|
|
import { Meteor } from 'meteor/meteor';
|
|
import 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);
|
|
});
|
|
});
|