@vimeo/vimeo
Version:
A Node.js library for the new Vimeo API.
517 lines (414 loc) • 19.6 kB
JavaScript
/* eslint-env mocha */
'use strict'
const Vimeo = require('../../lib/vimeo').Vimeo
const requestDefaults = require('../../lib/vimeo').request_defaults
const authEndpoints = require('../../lib/vimeo').authEndpoints
const fs = require('fs') // Needed for mocking
const http = require('http') // Needed for mocking
const https = require('https') // Needed for mocking
const events = require('events')
const expect = require('chai').expect
const sinon = require('sinon')
afterEach(() => {
sinon.restore()
})
describe('Vimeo.request using the Promise API', () => {
const vimeo = new Vimeo('id', 'secret', 'token')
let mockHttpRequest, mockHttpsRequest, mockReq, handleRequestStub
beforeEach(() => {
mockReq = new events.EventEmitter()
mockReq.on = sinon.fake(mockReq.on)
mockReq.end = sinon.fake()
mockReq.write = sinon.fake()
mockHttpRequest = sinon.fake.returns(mockReq)
sinon.replace(http, 'request', mockHttpRequest)
mockHttpsRequest = sinon.fake.returns(mockReq)
sinon.replace(https, 'request', mockHttpsRequest)
handleRequestStub = sinon.stub(vimeo, '_handleRequest').callsFake((resolve) => { resolve('Success.') })
})
it('returns an error if options has no path', async () => {
const error = sinon.match.instanceOf(Error).and(sinon.match.has('message', 'You must provide an API path.'))
await vimeo.request({}).catch((e) => sinon.assert.match(e, error))
})
it('parses options if passed as a string', async () => {
await vimeo.request('https://example.com:1234/path')
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ method: 'GET', path: '/path', host: 'example.com', port: '1234' }))
sinon.assert.calledOnce(handleRequestStub)
})
it('adds a leading slash if missing', async () => {
await vimeo.request({ path: 'path' })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ path: '/path' }))
})
it('uses https client when requested', async () => {
await vimeo.request({ protocol: 'https:', path: '/path' })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.notCalled(mockHttpRequest)
})
it('uses http client by default', async () => {
await vimeo.request({ protocol: 'proto:', path: '/path' })
sinon.assert.calledOnce(mockHttpRequest)
sinon.assert.notCalled(mockHttpsRequest)
})
it('sends body as JSON if content type is application/json', async () => {
await vimeo.request({ method: 'POST', path: '/path', query: { a: 'b' }, headers: { 'Content-Type': 'application/json' } })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ body: '{"a":"b"}' }))
})
it('sends form data as string if content type is application/x-www-form-urlencoded', async () => {
await vimeo.request({ method: 'POST', path: '/path', query: { a: 'b', c: 'd' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ body: 'a=b&c=d' }))
})
it('sends body as it is if content type is not application/x-www-form-urlencoded nor application/json', async () => {
await vimeo.request({ method: 'POST', path: '/path', body: 'text', headers: { 'Content-Type': 'text/plain' } })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ body: 'text' }))
})
it('sets the correct body Content-Length', async () => {
await vimeo.request({ method: 'POST', path: '/path', query: { a: 'b' }, headers: { 'Content-Type': 'application/json' } })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ headers: sinon.match.has('Content-Length', 9) }))
})
it('sets the correct body Content-Length', async () => {
await vimeo.request({ method: 'POST', path: '/path' })
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ headers: sinon.match.has('Content-Length', 0) }))
})
it('calls req.write with the body', async () => {
await vimeo.request({ method: 'POST', path: '/path', query: { a: 'b' } })
sinon.assert.calledOnce(mockReq.write)
sinon.assert.calledWith(mockReq.write, '{"a":"b"}')
})
it('doesn\'t call req.write if there is no body', async () => {
await vimeo.request({ method: 'POST', path: '/path' })
sinon.assert.notCalled(mockReq.write)
})
it('calls req.end()', async () => {
await vimeo.request({ path: '/path' })
sinon.assert.calledOnce(mockReq.end)
})
it('returns the correct response when the Promise is fullfilled', async () => {
const req = await vimeo.request({ method: 'POST', path: '/path' })
expect(req).to.equal('Success.')
})
it('returns the error object when the Promise is rejected', async () => {
handleRequestStub.resetBehavior()
const err = new Error('Request Error')
handleRequestStub.callsFake((resolve, reject) => {
reject(err)
})
await vimeo.request({ method: 'POST', path: '/path' }).catch((e) => {
expect(e).to.equal(err)
})
})
it('sets on error listener', async () => {
handleRequestStub.resetBehavior()
const req = vimeo.request({ path: '/path' }).catch((error) => {
sinon.assert.match(error, sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Error Emitted')))
})
mockReq.emit('error', new Error('Error Emitted'))
sinon.assert.calledOnce(mockReq.on)
sinon.assert.calledWith(mockReq.on, 'error', sinon.match.func)
return req
})
it('throws an error when the Promise rejects', async () => {
handleRequestStub.resetBehavior()
handleRequestStub.callsFake((resolve, reject) => { reject(new Error('Failure.')) })
const error = sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Failure.'))
await vimeo.request('https://example.com:1234/path').catch((e) => {
sinon.assert.match(e, error)
})
sinon.assert.calledOnce(mockHttpsRequest)
sinon.assert.calledWith(mockHttpsRequest, sinon.match({ method: 'GET', path: '/path', host: 'example.com', port: '1234' }))
sinon.assert.calledOnce(handleRequestStub)
})
})
describe('Vimeo.accessToken using the Promise API', () => {
const vimeo = new Vimeo('id', 'secret', 'token')
const CODE = 'code'
const REDIRECT_URI = 'redirectURI'
it('vimeo.request is called with the expected parameters', async () => {
const requestStub = sinon.stub(vimeo, 'request').resolves('Success.')
await vimeo.accessToken(CODE, REDIRECT_URI)
const expectedPayload = {
method: 'POST',
hostname: requestDefaults.hostname,
path: authEndpoints.accessToken,
query: {
grant_type: 'authorization_code',
code: CODE,
redirect_uri: REDIRECT_URI
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
sinon.assert.calledOnce(requestStub)
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
it('returns expected error response when the request fails', async () => {
const err = new Error('Request Error')
sinon.stub(vimeo, 'request').rejects(err)
await vimeo.accessToken(CODE, REDIRECT_URI).catch((error) => {
sinon.assert.match(error, err)
})
})
it('returns the expected response when the request is successful', async () => {
const body = 'body'
sinon.stub(vimeo, 'request').resolves(body)
await vimeo.accessToken(CODE, REDIRECT_URI).then((res) => {
sinon.assert.match(res, body)
})
})
})
describe('Vimeo.generateClientCredentials using the Promise API', () => {
const vimeo = new Vimeo('id', 'secret', 'token')
let requestStub
beforeEach(() => {
requestStub = sinon.stub(vimeo, 'request').resolves('Success.')
})
it('with `public` scope by default', async () => {
await vimeo.generateClientCredentials()
sinon.assert.calledOnce(requestStub)
sinon.assert.calledWith(requestStub, sinon.match({ query: sinon.match.has('scope', 'public') }))
})
it('with a space-separated list for scopes', async () => {
await vimeo.generateClientCredentials(['scope1', 'scope2'])
sinon.assert.calledOnce(requestStub)
sinon.assert.calledWith(requestStub, sinon.match({ query: sinon.match.has('scope', 'scope1 scope2') }))
})
it('with a space-separated list for scopes', async () => {
await vimeo.generateClientCredentials('scope1 scope2')
sinon.assert.calledOnce(requestStub)
sinon.assert.calledWith(requestStub, sinon.match({ query: sinon.match.has('scope', 'scope1 scope2') }))
})
it('with all defaults', async () => {
await vimeo.generateClientCredentials()
const expectedPayload = {
method: 'POST',
hostname: requestDefaults.hostname,
path: authEndpoints.clientCredentials,
query: {
grant_type: 'client_credentials'
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
sinon.assert.calledOnce(requestStub)
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
describe('when the callback is not passed in', () => {
it('request returns an error', async () => {
const error = new Error('Request Error')
requestStub.rejects(error)
await vimeo.generateClientCredentials('scope').catch(err => sinon.assert.match(err, error))
})
it('request is successful', async () => {
const body = 'body'
requestStub.resolves(body)
await vimeo.generateClientCredentials('scope').then(res => sinon.assert.match(res, body))
})
})
})
describe('Vimeo.upload using the Promise API', () => {
const FILE_NAME = '/real/file'
const FILE_SIZE = 24601
const vimeo = new Vimeo('id', 'secret', 'token')
const attempt = { body: { upload: { upload_link: 'body' }, uri: 'uri' } }
let requestStub
let mockProgressCallback
beforeEach(() => {
requestStub = sinon.stub(vimeo, 'request').resolves(attempt)
mockProgressCallback = sinon.fake()
})
it('throws an error if the file is inexistant', async () => {
const error = new Error('File Error')
const errFs = sinon.fake.throws(error)
sinon.replace(fs, 'statSync', errFs)
const vimeo = new Vimeo('id', 'secret', 'token')
await vimeo.upload(FILE_NAME, {}).catch(err => {
sinon.assert.match(err, error)
})
})
it('throws an error if the file parameter is an object', async () => {
const fileObject = { size: 123 }
await vimeo.upload(fileObject, {}).catch(err => sinon.assert.match(err, sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Please pass in a valid file path.'))))
})
describe('file exists', () => {
let mockTusUpload
beforeEach(() => {
const mockFs = sinon.fake.returns({ size: FILE_SIZE })
mockTusUpload = sinon.stub(vimeo, '_performTusUpload').callsFake((files, size, attempt, onComplete, onProgress, onError) => onComplete('uri'))
sinon.replace(fs, 'statSync', mockFs)
})
describe('always uses `tus` to upload', () => {
it('if upload.approach is not specified', async () => {
await vimeo.upload(FILE_NAME, {})
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
query: { upload: { approach: 'tus' } }
}
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
it('if upload.approach is not tus', async () => {
await vimeo.upload(FILE_NAME, { upload: { approach: 'not-tus' } })
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
query: { upload: { approach: 'tus' } }
}
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
})
it('request is called with the expected parameters', async () => {
await vimeo.upload(FILE_NAME, {})
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
method: 'POST',
path: '/me/videos?fields=uri,name,upload',
query: { upload: { approach: 'tus', size: FILE_SIZE } }
}
sinon.assert.calledWith(requestStub, expectedPayload)
})
it('calls the onError if request returned an error', async () => {
const error = new Error('Request Error')
requestStub.rejects(error)
await vimeo.upload(FILE_NAME, {}).catch(err => sinon.assert.match(err, sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Unable to initiate an upload. [Request Error]'))))
})
it('calls _performTusUpload with the expected parameters', async () => {
await vimeo.upload(FILE_NAME, {}, mockProgressCallback)
sinon.assert.calledOnce(mockTusUpload)
sinon.assert.calledWith(mockTusUpload, FILE_NAME, FILE_SIZE, attempt.body, sinon.match.typeOf('function'), mockProgressCallback, sinon.match.typeOf('function'))
})
it('shifts callbacks if param is not passed to the function', async () => {
await vimeo.upload(FILE_NAME, mockProgressCallback)
sinon.assert.calledOnce(mockTusUpload)
sinon.assert.calledWith(mockTusUpload, FILE_NAME, FILE_SIZE, attempt.body, sinon.match.typeOf('function'), mockProgressCallback, sinon.match.typeOf('function'))
})
it('returns uri when upload completes', async () => {
await vimeo.upload(FILE_NAME, mockProgressCallback).then((res) => sinon.assert.match(res, 'uri'))
})
it('returns error when upload fails', async () => {
const error = new Error('Upload Error')
mockTusUpload.resetBehavior()
mockTusUpload.callsFake((files, size, attempt, onComplete, onProgress, onError) => onError(error))
await vimeo.upload(FILE_NAME, mockProgressCallback).catch((err) => {
sinon.assert.match(err, error)
})
})
it('sents progress through the progressCallback during upload', async () => {
mockTusUpload.resetBehavior()
mockTusUpload.callsFake((files, size, attempt, onComplete, onProgress, onError) => {
onProgress('bytesUploaded', 'bytesTotal')
onProgress('bytesUploaded2', 'bytesTotal2')
onComplete()
})
await vimeo.upload(FILE_NAME, mockProgressCallback)
sinon.assert.calledTwice(mockProgressCallback)
sinon.assert.calledWith(mockProgressCallback.getCall(0), 'bytesUploaded', 'bytesTotal')
sinon.assert.calledWith(mockProgressCallback.getCall(1), 'bytesUploaded2', 'bytesTotal2')
})
})
})
describe('Vimeo.replace using the Promise API', () => {
const FILE_NAME = '/real/file/name'
const FILE_SIZE = 24601
const VIDEO_URI = '/videos/123456789'
const vimeo = new Vimeo('id', 'secret', 'token')
const attempt = { upload: { upload_link: 'body' } }
let requestStub
let mockProgressCallback
beforeEach(() => {
requestStub = sinon.stub(vimeo, 'request').resolves(attempt)
mockProgressCallback = sinon.fake()
})
it('throws an error if the file is inexistant', async () => {
const error = new Error('File Error')
const errFs = sinon.fake.throws(error)
sinon.replace(fs, 'statSync', errFs)
const vimeo = new Vimeo('id', 'secret', 'token')
await vimeo.replace(FILE_NAME, VIDEO_URI, {}).catch(err => {
sinon.assert.match(err, error)
})
})
it('throws an error if the file parameter is an object', async () => {
const fileObject = { size: 123, name: 'name' }
await vimeo.replace(fileObject, VIDEO_URI, {}).catch(err => sinon.assert.match(err, sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Please pass in a valid file path.'))))
})
describe('file exists', () => {
let mockTusUpload
beforeEach(() => {
const mockFs = sinon.fake.returns({ size: FILE_SIZE })
mockTusUpload = sinon.stub(vimeo, '_performTusUpload').callsFake((files, size, attempt, onComplete, onProgress, onError) => onComplete('uri'))
sinon.replace(fs, 'statSync', mockFs)
})
describe('always uses `tus` to upload', () => {
it('if upload.approach is not specified', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, {})
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
query: { upload: { approach: 'tus' } }
}
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
it('if upload.approach is not tus', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, { upload: { approach: 'not-tus' } })
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
query: { upload: { approach: 'tus' } }
}
sinon.assert.calledWith(requestStub, sinon.match(expectedPayload))
})
})
it('request is called with the expected parameters', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, {})
sinon.assert.calledOnce(requestStub)
const expectedPayload = {
method: 'POST',
path: VIDEO_URI + '/versions?fields=upload',
query: { file_name: 'name', upload: { approach: 'tus', size: FILE_SIZE } }
}
sinon.assert.calledWith(requestStub, expectedPayload)
})
it('calls the onError if request returned an error', async () => {
const error = new Error('Request Error')
requestStub.rejects(error)
await vimeo.replace(FILE_NAME, VIDEO_URI, {}).catch(err => sinon.assert.match(err, sinon.match.instanceOf(Error).and(sinon.match.has('message', 'Unable to initiate an upload. [Request Error]'))))
})
it('calls _performTusUpload with the expected parameters', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, {}, mockProgressCallback)
sinon.assert.calledOnce(mockTusUpload)
sinon.assert.calledWith(mockTusUpload, FILE_NAME, FILE_SIZE, attempt.body, sinon.match.typeOf('function'), mockProgressCallback, sinon.match.typeOf('function'))
})
it('shifts callbacks if param is not passed to the function', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, mockProgressCallback)
sinon.assert.calledOnce(mockTusUpload)
sinon.assert.calledWith(mockTusUpload, FILE_NAME, FILE_SIZE, attempt.body, sinon.match.typeOf('function'), mockProgressCallback, sinon.match.typeOf('function'))
})
it('returns uri when upload completes', async () => {
await vimeo.replace(FILE_NAME, VIDEO_URI, mockProgressCallback).then((res) => sinon.assert.match(res, 'uri'))
})
it('returns error when replace fails', async () => {
const error = new Error('Replace Error')
mockTusUpload.resetBehavior()
mockTusUpload.callsFake((files, size, attempt, onComplete, onProgress, onError) => onError(error))
await vimeo.replace(FILE_NAME, VIDEO_URI, mockProgressCallback).catch((err) => {
sinon.assert.match(err, error)
})
})
it('sents progress through the progressCallback during upload', async () => {
mockTusUpload.resetBehavior()
mockTusUpload.callsFake((files, size, attempt, onComplete, onProgress, onError) => {
onProgress('bytesUploaded', 'bytesTotal')
onProgress('bytesUploaded2', 'bytesTotal2')
onComplete()
})
await vimeo.replace(FILE_NAME, VIDEO_URI, mockProgressCallback)
sinon.assert.calledTwice(mockProgressCallback)
sinon.assert.calledWith(mockProgressCallback.getCall(0), 'bytesUploaded', 'bytesTotal')
sinon.assert.calledWith(mockProgressCallback.getCall(1), 'bytesUploaded2', 'bytesTotal2')
})
})
})