2 /* eslint-disable capitalized-comments */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const Manager
= require('../../src/manager');
10 const common
= require('../../src/common');
11 const Enum
= require('../../src/enum');
12 const { ServeStaticFile
, SlugGeneratorExhausted
} = require('../../src/errors');
14 const noExpectedException
= 'did not get expected exception';
16 describe('Manager', function () {
20 beforeEach(function () {
27 context: async (fn
) => await
fn({}),
28 transaction: async (_dbCtx
, fn
) => await
fn({}),
29 getLinkById: sinon
.stub(),
30 accessLink: sinon
.stub(),
31 getLinkByUrl: sinon
.stub(),
32 expireLink: sinon
.stub(),
33 insertLink: sinon
.stub(),
34 getAllLinks: sinon
.stub(),
39 setHeader: sinon
.stub(),
44 manager
= new Manager(stubLogger
, stubDb
, options
);
47 afterEach(function () {
51 it('instantiates', function () {
55 it('defaults options', function () {
56 manager
= new Manager({}, {});
59 describe('rootContent', function () {
60 it('generates content for empty context', function () {
61 const result
= manager
.rootContent(ctx
);
62 assert(result
.length
);
64 it('generates json content', function () {
65 ctx
.responseType
= Enum
.ContentType
.ApplicationJson
;
66 const result
= manager
.rootContent(ctx
);
67 assert(result
.length
);
70 it('generates html content', function () {
71 ctx
.responseType
= Enum
.ContentType
.TextHTML
;
72 const result
= manager
.rootContent(ctx
);
73 assert(result
.length
);
75 it('includes context fields', function () {
76 ctx
.createdLink
= 'http://example.com/foo';
77 ctx
.authToken
= 'token';
78 ctx
.message
= 'message';
79 ctx
.sourceLink
= 'http://source.example.com/';
80 const result
= manager
.rootContent(ctx
);
81 assert(result
.length
);
85 describe('getRoot', function () {
87 beforeEach(function () {
88 sinon
.stub(common
, 'isClientCached');
91 it('normal response', async
function () {
92 common
.isClientCached
.returns(false);
93 await manager
.getRoot(req
, res
, ctx
);
94 assert(res
.end
.called
);
96 it('repeat response', async
function () {
97 manager
.startTime
= (new Date()).toGMTString();
98 common
.isClientCached
.returns(true);
99 await manager
.getRoot(req
, res
, ctx
);
100 assert(res
.end
.called
);
102 it('cached response', async
function () {
103 common
.isClientCached
.returns(true);
104 await manager
.getRoot(req
, res
, ctx
);
105 assert(res
.end
.called
);
106 assert
.strictEqual(res
.statusCode
, 304);
110 describe('_getNewIdentifier', function () {
111 const url
= 'http://example.com/bar';
113 beforeEach(function () {
115 nextStub
= sinon
.stub();
116 manager
.db
.getLinkById
.onCall(0).resolves({ id:'existing' }).onCall(1).resolves();
117 sinon
.stub(manager
, 'makeSlugGenerator').callsFake(() => {
123 it('gets identifiers', async
function () {
124 nextStub
.resolves({ value: 'slug', done: false });
125 const result
= await manager
._getNewIdentifier(dbCtx
, url
);
127 assert
.strictEqual(nextStub
.callCount
, 2);
129 it('handles empty slug', async
function () {
130 nextStub
.resolves({ value: '', done: false });
132 await manager
._getNewIdentifier(dbCtx
, url
);
133 assert
.fail(noExpectedException
);
135 assert(e
instanceof SlugGeneratorExhausted
, noExpectedException
);
138 it('handles end of generator', async
function () {
139 nextStub
.resolves({ value: 'blah', done: true });
141 await manager
._getNewIdentifier(dbCtx
, url
);
142 assert
.fail(noExpectedException
);
144 assert(e
instanceof SlugGeneratorExhausted
, noExpectedException
);
147 }); // _getNewIdentifier
149 describe('_validateContextURL', function () {
150 it('allows admin to create local static link', function () {
151 ctx
.sourceLink
= `${manager.staticDirectory}file.txt`;
152 ctx
.authenticationId
= 'awoo';
153 manager
._validateContextURL(ctx
);
155 it('accepts valid url', function () {
156 ctx
.sourceLink
= 'http://example.com/file.txt';
157 manager
._validateContextURL(ctx
);
159 it('rejects missing url', function () {
161 manager
._validateContextURL(ctx
);
162 assert
.fail(noExpectedException
);
164 assert
.strictEqual(e
.message
, Enum
.ErrorResponse
.InvalidURLParameter
.errorMessage
, noExpectedException
);
167 it('rejects invalid url', function () {
168 ctx
.sourceLink
= 'not a url';
170 manager
._validateContextURL(ctx
);
171 assert
.fail(noExpectedException
);
173 assert
.strictEqual(e
.message
, Enum
.ErrorResponse
.InvalidURLParameter
.errorMessage
, noExpectedException
);
176 }); // _validateContextURL
178 describe('getById', function () {
180 beforeEach(function () {
183 it('handles missing link', async
function () {
185 await manager
.getById(res
, ctx
);
186 assert
.fail(noExpectedException
);
188 assert
.strictEqual(e
.message
, 'Not Found');
191 it('handles expired link', async
function () {
195 manager
.db
.accessLink
.resolves(link
)
197 await manager
.getById(res
, ctx
);
198 assert
.fail(noExpectedException
);
200 assert
.strictEqual(e
.message
, 'Gone');
203 it('handles local static link', async
function () {
204 const file
= 'foop.txt';
206 url: `${manager.staticDirectory}${file}`,
208 manager
.db
.accessLink
.resolves(link
);
210 await manager
.getById(res
, ctx
);
211 assert
.fail(noExpectedException
);
213 assert(e
instanceof ServeStaticFile
);
214 assert
.strictEqual(e
.file
, file
);
217 it('redirects a link', async
function () {
219 url: 'http://example.com/awoo',
221 manager
.db
.accessLink
.resolves(link
);
222 await manager
.getById(res
, ctx
);
223 assert
.strictEqual(res
.statusCode
, 307);
227 describe('postRoot', function () {
228 beforeEach(function () {
231 it('requires url parameter', async
function () {
233 await manager
.postRoot(res
, ctx
);
234 assert
.fail(noExpectedException
);
236 assert
.strictEqual(e
.message
, 'Bad Request');
239 it('creates a link', async
function () {
240 ctx
.parsedBody
.url
= 'http://example.com/insert';
241 await manager
.postRoot(res
, ctx
);
242 assert(manager
.db
.insertLink
.called
);
243 assert(res
.end
.called
);
245 it('returns existing link', async
function () {
246 ctx
.parsedBody
.url
= 'http://example.com/existing';
247 const existingLink
= {
249 sourceLink: ctx
.parsedBody
.url
,
251 manager
.db
.getLinkByUrl
.resolves(existingLink
);
252 await manager
.postRoot(res
, ctx
);
253 assert(!manager
.db
.insertLink
.called
);
254 assert(!manager
.db
.expireLink
.called
);
255 assert(res
.end
.called
);
257 it('restores expired link', async
function () {
258 ctx
.parsedBody
.url
= 'http://example.com/expired';
259 const existingLink
= {
261 sourceLink: ctx
.parsedBody
.url
,
264 manager
.db
.getLinkByUrl
.resolves(existingLink
);
265 await manager
.postRoot(res
, ctx
);
266 assert(!manager
.db
.insertLink
.called
);
267 assert(manager
.db
.expireLink
.called
);
268 assert(res
.end
.called
);
272 describe('putById', function () {
274 beforeEach(function () {
275 url
= 'http://example.com/put';
278 it('requires url parameter', async
function () {
280 await manager
.putById(res
, ctx
);
281 assert
.fail(noExpectedException
);
283 assert
.strictEqual(e
.message
, 'Bad Request');
286 it('updates existing', async
function () {
287 ctx
.parsedBody
.url
= url
;
288 const existingLink
= {
291 manager
.db
.getLinkById
.resolves(existingLink
);
292 await manager
.putById(res
, ctx
);
293 assert(manager
.db
.insertLink
.called
);
294 assert(res
.end
.called
);
296 it('does not create without admin', async
function () {
297 ctx
.parsedBody
.url
= url
;
299 await manager
.putById(res
, ctx
);
300 assert
.fail(noExpectedException
);
302 assert
.strictEqual(e
.message
, 'Forbidden');
305 it('allows admin creation', async
function () {
306 ctx
.parsedBody
.url
= url
;
307 ctx
.authenticationId
= 'blah';
308 await manager
.putById(res
, ctx
);
309 assert(manager
.db
.insertLink
.called
);
310 assert
.strictEqual(res
.statusCode
, 201);
311 assert(res
.end
.called
);
315 describe('deleteById', function () {
316 it('handles missing id', async
function () {
318 await manager
.deleteById(res
, ctx
);
319 assert
.fail(noExpectedException
);
321 assert
.strictEqual(e
.message
, 'Not Found');
324 it('expires link', async
function () {
325 const existingLink
= {
328 manager
.db
.getLinkById
.resolves(existingLink
);
329 await manager
.deleteById(res
, ctx
);
330 assert(manager
.db
.expireLink
.called
);
331 assert
.strictEqual(res
.statusCode
, 204);
333 it('ignores expired link', async
function () {
334 const existingLink
= {
338 manager
.db
.getLinkById
.resolves(existingLink
);
339 await manager
.deleteById(res
, ctx
);
340 assert(!manager
.db
.expireLink
.called
);
341 assert
.strictEqual(res
.statusCode
, 304);
345 describe('infoContent', function () {
347 beforeEach(function () {
350 lastAccess: 1604155861,
354 it('generates info', function () {
355 details
.expires
= 1604155862;
356 const result
= manager
.infoContent(ctx
, details
);
359 it('generates json info', function () {
360 ctx
.responseType
= Enum
.ContentType
.ApplicationJson
;
361 const result
= manager
.infoContent(ctx
, details
);
364 it('generates html info', function () {
365 ctx
.responseType
= Enum
.ContentType
.TextHTML
;
366 const result
= manager
.infoContent(ctx
, details
);
371 describe('getByIdInfo', function () {
372 it('handles missing link', async
function () {
374 await manager
.getByIdInfo(res
, ctx
);
375 assert
.fail(noExpectedException
);
377 assert
.strictEqual(e
.message
, 'Not Found');
380 it('gets link', async
function () {
381 const existingLink
= {
384 manager
.db
.getLinkById
.resolves(existingLink
);
385 await manager
.getByIdInfo(res
, ctx
);
386 assert(res
.end
.called
);
390 describe('reportContent', function () {
392 it('generates report', function () {
397 lastAccess: 1604155861,
398 url: 'http://example.com/awoo',
401 const result
= manager
.reportContent(ctx
, links
);
404 it('generates json report', function () {
406 ctx
.responseType
= Enum
.ContentType
.ApplicationJson
;
407 const result
= manager
.reportContent(ctx
, links
);
410 it('generates html report', function () {
412 ctx
.responseType
= Enum
.ContentType
.TextHTML
;
413 const result
= manager
.reportContent(ctx
, links
);
418 describe('getAdminReport', function () {
419 it('does links', async
function () {
421 manager
.db
.getAllLinks
.resolves(links
);
422 await manager
.getAdminReport(res
, ctx
);
423 assert(res
.end
.called
);
425 }); // getAdminReport