import { Eltro as t, assert, stub } from 'eltro' import { FileResponse } from '../flaska.mjs' import { createCtx } from './helper.mjs' t.test('should add path and stat', function() { const assertPath = 'some/path/here' const assertStat = { a: 1 } let fileRes = new FileResponse(assertPath, assertStat) assert.strictEqual(fileRes.filepath, assertPath) assert.strictEqual(fileRes.stat, assertStat) }) t.describe('CreateReader()', function() { t.describe('return fileReader', function() { const assertPath = '/some/path/here.png' const assertBody = { a: 1 } const assertSize = 12498 const assertmTime = new Date('2021-04-02T11:22:33.777') const roundedModified = new Date('2021-04-02T11:22:33') const assertIno = 3569723027 const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } let tests = [ [ 'if no range', function() {}, ], [ 'if range start is higher than end', function(headers) { headers['range'] = 'bytes=2000-1000' }, ], [ 'if pre-condition if-match passes', function(headers) { headers['if-match'] = assertEtag }, ], [ 'if pre-condition if-unmodified-since passes', function(headers) { headers['if-unmodified-since'] = roundedModified.toUTCString(); }, ], [ 'if both pre-condition pass', function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() + 1000).toUTCString(); headers['if-match'] = assertEtag }, ], [ 'if range is specified but if-range etag does not match', function(headers) { headers['if-range'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"` headers['range'] = 'bytes=1000-2000' } ], [ 'if range is specified but if-range modified by is older', function(headers) { headers['if-range'] = `${new Date(roundedModified.getTime() - 1000).toUTCString()}` headers['range'] = 'bytes=1000-2000' } ], [ 'if range is specified but if-range is garbage', function(headers) { headers['if-range'] = `asdf` headers['range'] = 'bytes=1000-2000' } ], ] tests.forEach(function(test){ t.test(test[0], function() { const stubCreateReadStream = stub().returns(assertBody) let ctx = createCtx() test[1](ctx.req.headers) ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, assertBody) assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath) assert.deepStrictEqual(stubCreateReadStream.firstCall[1], {}) assert.strictEqual(ctx.headers['Etag'], assertEtag) assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString()) assert.strictEqual(ctx.headers['Content-Length'], assertSize) assert.strictEqual(ctx.type, 'image/png') assert.notOk(ctx.headers['Content-Range']) assert.strictEqual(ctx.status, 200) }) }) }) t.test('return fileReader with proper length with range', function() { const assertPath = '/some/path/here.jpg' const assertBody = { a: 1 } const assertSize = 2000 const assertmTime = new Date('2021-04-03T11:22:33.777') const assertIno = 3569723027 const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } const stubCreateReadStream = stub().returns(assertBody) let ctx = createCtx() ctx.req.headers['range'] = 'bytes=0-1023' ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, assertBody) assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath) assert.strictEqual(stubCreateReadStream.firstCall[1].start, 0) assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1023) assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`) assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString()) assert.strictEqual(ctx.headers['Content-Length'], 1024) assert.strictEqual(ctx.type, 'image/jpeg') assert.strictEqual(ctx.headers['Content-Range'], `0-1023/${assertSize}`) assert.strictEqual(ctx.status, 206) }) t.test('should not call createReadStream if HEAD request', function() { const assertPath = '/some/path/here.png' const assertNotBody = { a: 1 } const assertSize = 12498 const assertmTime = new Date('2021-04-02T11:22:33.777') const roundedModified = new Date('2021-04-02T11:22:33') const assertIno = 3569723027 const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } let tests = [ [ 'if no range', function() {}, ], [ 'if range start is higher than end', function(headers) { headers['range'] = 'bytes=2000-1000' }, ], [ 'if pre-condition if-match passes', function(headers) { headers['if-match'] = assertEtag }, ], [ 'if pre-condition if-unmodified-since passes', function(headers) { headers['if-unmodified-since'] = roundedModified.toUTCString(); }, ], [ 'if both pre-condition pass', function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() + 1000).toUTCString(); headers['if-match'] = assertEtag }, ], [ 'if range is specified but if-range etag does not match', function(headers) { headers['if-range'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"` headers['range'] = 'bytes=1000-2000' } ], [ 'if range is specified but if-range modified by is older', function(headers) { headers['if-range'] = `${new Date(roundedModified.getTime() - 1000).toUTCString()}` headers['range'] = 'bytes=1000-2000' } ], [ 'if range is specified but if-range is garbage', function(headers) { headers['if-range'] = `asdf` headers['range'] = 'bytes=1000-2000' } ], ] tests.forEach(function(test){ const stubCreateReadStream = stub().returns(assertNotBody) let ctx = createCtx({ method: 'HEAD', }) test[1](ctx.req.headers) ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, null) assert.notOk(stubCreateReadStream.called) assert.strictEqual(ctx.headers['Etag'], assertEtag) assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString()) assert.strictEqual(ctx.headers['Content-Length'], assertSize) assert.strictEqual(ctx.type, 'image/png') assert.notOk(ctx.headers['Content-Range']) assert.strictEqual(ctx.status, 200) }) const testStart = [0, 1000, 1999] testStart.forEach(function(start) { const stubCreateReadStream = stub() let ctx = createCtx({ method: 'HEAD', }) ctx.req.headers['range'] = 'bytes=' + start + '-' ctx.body = new FileResponse('file.png', stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, null) assert.notOk(stubCreateReadStream.called) assert.strictEqual(ctx.headers['Content-Length'], assertSize - start) assert.strictEqual(ctx.headers['Content-Range'], `${start}-${assertSize - 1}/${assertSize}`) assert.strictEqual(ctx.status, 206) }) }) t.test('return fileReader with proper length with range and if-range passes', function() { const assertPath = '/some/path/here.jpg' const assertBody = { a: 1 } const assertSize = 2000 const assertmTime = new Date('2021-04-03T11:22:33.777') const roundedModified = new Date('2021-04-03T11:22:33') const assertIno = 3569723027 const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` let tests = [ function(headers) { headers['if-range'] = roundedModified.toUTCString() }, function(headers) { headers['if-range'] = assertEtag }, ] tests.forEach(function(check, i) { const stubCreateReadStream = stub().returns(assertBody) let ctx = createCtx() ctx.req.headers['range'] = 'bytes=0-1023' check(ctx.req.headers) ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, assertBody) assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath) assert.strictEqual(stubCreateReadStream.firstCall[1].start, 0, `start missing in test ${i + 1}`) assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1023) assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`) assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString()) assert.strictEqual(ctx.headers['Content-Length'], 1024) assert.strictEqual(ctx.type, 'image/jpeg') assert.strictEqual(ctx.headers['Content-Range'], `0-1023/${assertSize}`) assert.strictEqual(ctx.status, 206) }) }) t.test('return fileReader with proper start if only start is specified', function() { const assertSize = 2000 const stat = { size: assertSize, mtime: new Date('2021-04-03T11:22:33.777'), ino: 111, } const testStart = [0, 1000, 1999] testStart.forEach(function(start) { const stubCreateReadStream = stub() let ctx = createCtx() ctx.req.headers['range'] = 'bytes=' + start + '-' ctx.body = new FileResponse('file.png', stat) ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(stubCreateReadStream.firstCall[1].start, start) assert.strictEqual(stubCreateReadStream.firstCall[1].end, assertSize - 1) assert.strictEqual(ctx.headers['Content-Length'], assertSize - start) assert.strictEqual(ctx.headers['Content-Range'], `${start}-${assertSize - 1}/${assertSize}`) assert.strictEqual(ctx.status, 206) }) }) t.test('should default to end if end overflows but start is valid', function() { const assertSize = 2000 const stat = { size: assertSize, mtime: new Date('2021-04-03T11:22:33.777'), ino: 111, } const testEnd = [2000, 3000, 99999] testEnd.forEach(function(end) { const stubCreateReadStream = stub() let ctx = createCtx() ctx.req.headers['range'] = 'bytes=1000-' + end ctx.body = new FileResponse('file.png', stat) ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(stubCreateReadStream.firstCall[1].start, 1000) assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1999) assert.strictEqual(ctx.headers['Content-Length'], 1000) assert.strictEqual(ctx.headers['Content-Range'], `1000-${assertSize - 1}/${assertSize}`) assert.strictEqual(ctx.status, 206) }) }) t.test('should throw 416 if start is out of range', function() { const stubCreateReadStream = stub() let tests = [1000, 2000, 9999] tests.forEach(function(start) { let ctx = createCtx() ctx.body = new FileResponse('file.png', { size: 1000, mtime: new Date('2021-04-03T11:22:33.777'), ino: 111, }) ctx.req.headers['range'] = `bytes=${start}-` assert.throws(function() { ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) }, function(err) { assert.strictEqual(err.status, 416) assert.match(err.message, new RegExp(1000)) assert.match(err.message, new RegExp(start)) assert.ok(ctx.headers['Etag']) assert.notOk(stubCreateReadStream.called) assert.notOk(ctx.headers['Last-Modified']) assert.notOk(ctx.headers['Content-Length']) assert.notOk(ctx.type) assert.notOk(ctx.headers['Content-Range']) return true }) }) }) t.test('should return 304 if etag is found', function() { const assertPath = '/some/path/here.png' const assertNotBody = { a: 1 } const assertSize = 12498 const assertmTime = new Date('2021-04-02T11:22:33.777') const assertIno = 3569723027 const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` let tests = [ assertEtag, `"asdf", "herp", ${assertEtag}`, `"asdf", ${assertEtag}, "bla"`, `${assertEtag}, "hello world"`, `"asdf","herp",${assertEtag}`, `"asdf",${assertEtag},"bla"`, `${assertEtag},"hello world"`, ] tests.forEach(function(check) { const stubCreateReadStream = stub().returns(assertNotBody) let ctx = createCtx() ctx.req.headers['if-none-match'] = check ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, null) assert.strictEqual(ctx.status, 304) assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`) assert.notOk(stubCreateReadStream.called) assert.notOk(ctx.headers['Last-Modified']) assert.notOk(ctx.headers['Content-Length']) assert.notOk(ctx.type) assert.notOk(ctx.headers['Content-Range']) }) }) t.test('should return 304 if-last-modified is found', function() { const assertPath = '/some/path/here.png' const assertNotBody = { a: 1 } const assertSize = 12498 const assertmTime = new Date('2021-04-02T11:22:33.777') const assertIno = 3569723027 const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } let tests = [ assertmTime.toUTCString(), 'Fri, 02 Apr 2021 11:22:33 GMT', 'Fri, 02 Apr 2021 11:22:34 GMT', 'Fri, 02 Apr 2022 11:22:32 GMT', ] tests.forEach(function(check) { const stubCreateReadStream = stub().returns(assertNotBody) let ctx = createCtx() ctx.body = new FileResponse(assertPath, stat) ctx.req.headers['if-modified-since'] = assertmTime.toUTCString() let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, null) assert.strictEqual(ctx.status, 304) assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`) assert.notOk(stubCreateReadStream.called) assert.notOk(ctx.headers['Last-Modified']) assert.notOk(ctx.headers['Content-Length']) assert.notOk(ctx.type) assert.notOk(ctx.headers['Content-Range']) }) }) t.test('should return 200 if etag or modified-by is not found', function() { const assertPath = '/some/path/here.png' const assertBody = { a: 1 } const assertSize = 12498 const assertmTime = new Date('2021-04-02T11:22:33.777') const assertIno = 3569723027 const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` const roundedModified = new Date('2021-04-02T11:22:33') let tests = [ function(headers) { headers['if-none-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"`; }, function(headers) { headers['if-none-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() - 1}"`; }, function(headers) { headers['if-none-match'] = `"asdf"`; }, function(headers) { headers['if-none-match'] = `"asdf"`; headers['if-modified-since'] = roundedModified.toUTCString(); }, function(headers) { headers['if-modified-since'] = new Date(roundedModified.getTime() - 1).toUTCString(); }, function(headers) { headers['if-modified-since'] = 'asdfs' }, ] tests.forEach(function(check, i) { const stubCreateReadStream = stub().returns(assertBody) let ctx = createCtx() check(ctx.req.headers) ctx.body = new FileResponse(assertPath, stat) let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) assert.strictEqual(checkBody, assertBody, 'missing body in test ' + (i + 1)) assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath) assert.deepStrictEqual(stubCreateReadStream.firstCall[1], {}) assert.strictEqual(ctx.headers['Etag'], assertEtag) assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString()) assert.strictEqual(ctx.headers['Content-Length'], assertSize) assert.strictEqual(ctx.type, 'image/png') assert.notOk(ctx.headers['Content-Range']) assert.strictEqual(ctx.status, 200) }) }) t.test('should return 412 if precondition fails', function() { const stubCreateReadStream = stub() const assertmTime = new Date('2021-04-02T11:22:33.777') const roundedModified = new Date('2021-04-02T11:22:33') const assertIno = 3569723027 const assertSize = 12498 const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"` const stat = { size: assertSize, mtime: assertmTime, ino: assertIno, } let tests = [ function(headers) { headers['if-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"` }, function(headers) { headers['if-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() - 1}"` }, function(headers) { headers['if-match'] = `asdf` }, function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() - 1).toUTCString(); }, function(headers) { headers['if-unmodified-since'] = 'asdf'; }, function(headers) { headers['if-match'] = assertEtag; headers['if-unmodified-since'] = 'asdf'; }, function(headers) { headers['if-match'] = assertEtag; headers['if-unmodified-since'] = new Date(roundedModified.getTime() - 1000).toUTCString(); }, function(headers) { headers['if-match'] = '"bla"'; headers['if-unmodified-since'] = roundedModified.toUTCString(); }, ] tests.forEach(function(check) { let ctx = createCtx() ctx.body = new FileResponse('file.png', stat) check(ctx.req.headers) assert.throws(function() { ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream }) }, function(err) { assert.strictEqual(err.status, 412) assert.notOk(stubCreateReadStream.called) assert.notOk(ctx.headers['Last-Modified']) assert.notOk(ctx.headers['Content-Length']) assert.notOk(ctx.type) assert.notOk(ctx.headers['Content-Range']) return true }) }) }) })