Skip to content

Commit

Permalink
introduce upload entity and related tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Benedikt Rötsch committed Feb 28, 2017
1 parent c3cb996 commit a4805b4
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 23 deletions.
24 changes: 22 additions & 2 deletions lib/create-space-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export default function createSpaceApi ({
const {wrapSpaceMembership, wrapSpaceMembershipCollection} = entities.spaceMembership
const {wrapApiKey, wrapApiKeyCollection} = entities.apiKey
const {wrapEditorInterface} = entities.editorInterface
const {wrapUpload} = entities.upload

/**
* Space instances.
Expand Down Expand Up @@ -576,7 +577,7 @@ export default function createSpaceApi ({
* Creates a Upload.
* @memberof ContentfulSpaceAPI
* @param {object} data - Can be a string, an ArrayBuffer or a Stream.
* @return {Promise<Object>} Upload object containing information about the uploaded file.
* @return {Promise<Upload>} Upload object containing information about the uploaded file.
* @example
* const uploadStream = createReadStream('path/to/filename_english.jpg')
* space.createUpload(uploadStream)
Expand All @@ -591,7 +592,24 @@ export default function createSpaceApi ({
'Content-Type': 'application/octet-stream'
}
})
.then(uploadResponse => uploadResponse.data)
.then(uploadResponse => {
return wrapUpload(httpUpload, uploadResponse.data)
})
.catch(errorHandler)
}

/**
* Gets an Upload
* @memberof ContentfulSpaceAPI
* @param {string} id
* @return {Promise<Upload>} Promise for an Upload
* @example
* space.getUpload('uploadId')
* .then(upload => console.log(upload))
*/
function getUpload (id) {
return httpUpload.get('uploads/' + id)
.then((response) => wrapUpload(http, response.data))
.catch(errorHandler)
}

Expand Down Expand Up @@ -896,6 +914,8 @@ export default function createSpaceApi ({
createAsset: createAsset,
createAssetWithId: createAssetWithId,
createAssetFromFiles: createAssetFromFiles,
getUpload: getUpload,
createUpload: createUpload,
getLocale: getLocale,
getLocales: getLocales,
createLocale: createLocale,
Expand Down
4 changes: 3 additions & 1 deletion lib/entities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as webhook from './webhook'
import * as spaceMembership from './space-membership'
import * as role from './role'
import * as apiKey from './api-key'
import * as upload from './upload'

export default {
space,
Expand All @@ -19,5 +20,6 @@ export default {
webhook,
spaceMembership,
role,
apiKey
apiKey,
upload
}
59 changes: 59 additions & 0 deletions lib/entities/upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import cloneDeep from 'lodash/cloneDeep'
import { freezeSys, toPlainObject } from 'contentful-sdk-core'
import enhanceWithMethods from '../enhance-with-methods'
import {
createDeleteEntity
} from '../instance-actions'

/**
* @typedef {Upload} Upload
* @property {Object} sys - Standard system metadata with additional asset specific properties
* @property {string} sys.id - The id of the upload
* @property {function(): Promise<Asset>} delete - Deletes an upload
* @property {function(): Object} toPlainObject - Returns this Asset as a plain JS object
* @example
*
* // require contentful-management
* var contentfulManagement = require('contentful-management')
* var client = contentfulManagement.createClient({
* // This is the access token for this space. Normally you get both ID and the token in the Contentful web app
* accessToken: 'YOUR_ACCESS_TOKEN'
* })
*
* var space = await client.getSpace('SPACE_ID')
* const uploadStream = createReadStream('path/to/filename_english.jpg')
* space.createUpload(uploadStream)
* .then((upload) => {
* // Delte upload again
* return upload.delete()
* })
* .catch(err => console.log(err))
*/

function createUploadApi (http) {
return {
/**
* Deletes this object on the server.
* @memberof Upload
* @func delete
* @return {Promise} Promise for the deletion. It contains no data, but the Promise error case should be handled.
* @example
*/
delete: createDeleteEntity({
http: http,
entityPath: 'uploads'
})
}
}

/**
* @private
* @param {Object} http - HTTP client instance
* @param {Object} data - Raw upload data
* @return {Asset} Wrapped upload data
*/
export function wrapUpload (http, data) {
const upload = toPlainObject(cloneDeep(data))
enhanceWithMethods(upload, createUploadApi(http))
return freezeSys(upload)
}
69 changes: 51 additions & 18 deletions test/unit/create-space-api-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import test from 'blue-tape'
import sinon from 'sinon'
import { Promise } from 'es6-promise'

import { toPlainObject } from 'contentful-sdk-core'
Expand All @@ -9,6 +8,7 @@ import {
editorInterfaceMock,
assetMock,
assetWithFilesMock,
uploadMock,
entryMock,
localeMock,
webhookMock,
Expand All @@ -30,10 +30,12 @@ import {
function setup (promise) {
const entitiesMock = setupEntitiesMock(createSpaceApiRewireApi)
const httpMock = setupHttpMock(promise)
const api = createSpaceApi({ http: httpMock, httpUpload: httpMock })
const httpUploadMock = setupHttpMock(promise)
const api = createSpaceApi({ http: httpMock, httpUpload: httpUploadMock })
return {
api,
httpMock,
httpUploadMock,
entitiesMock
}
}
Expand Down Expand Up @@ -304,22 +306,17 @@ test('API call createAssetWithId fails', (t) => {
})

test('API call createAssetFromFiles', (t) => {
const { api, httpMock, entitiesMock } = setup(Promise.resolve({}))
const { api, httpMock, httpUploadMock, entitiesMock } = setup(Promise.resolve({}))

function makeUploadResponse () {
return Promise.resolve({
data: {
sys: {
id: 'some_random_id'
}
entitiesMock.upload.wrapUpload.returns(Promise.resolve(uploadMock))
httpUploadMock.post.returns(Promise.resolve({
data: {
sys: {
id: 'some_random_id'
}
})
}

httpMock.post = sinon.stub()
httpMock.post.onCall(0).returns(makeUploadResponse())
httpMock.post.onCall(1).returns(makeUploadResponse())
httpMock.post.onCall(2).returns(Promise.resolve({
}
}))
httpMock.post.returns(Promise.resolve({
data: assetWithFilesMock
}))

Expand All @@ -340,12 +337,48 @@ test('API call createAssetFromFiles', (t) => {
}
})
.then(() => {
t.equals(httpMock.post.args[0][1], '<svg><path fill="red" d="M50 50h150v50H50z"/></svg>', 'uploads file #1 to upload endpoint')
t.equals(httpMock.post.args[1][1], '<svg><path fill="blue" d="M50 50h150v50H50z"/></svg>', 'uploads file #2 to upload endpoint')
t.equals(httpUploadMock.post.args[0][1], '<svg><path fill="red" d="M50 50h150v50H50z"/></svg>', 'uploads file #1 to upload endpoint')
t.equals(httpUploadMock.post.args[1][1], '<svg><path fill="blue" d="M50 50h150v50H50z"/></svg>', 'uploads file #2 to upload endpoint')
t.deepEqual(entitiesMock.asset.wrapAsset.args[0][1], assetWithFilesMock, 'wrapAsset was called with proper asset')
})
})

test('API call getUpload', (t) => {
makeGetEntityTest(t, setup, teardown, {
entityType: 'upload',
mockToReturn: uploadMock,
methodToTest: 'getUpload'
})
})

test('API call getUpload fails', (t) => {
makeEntityMethodFailingTest(t, setup, teardown, {
methodToTest: 'getUpload'
})
})

test('API call createUpload', (t) => {
const { api, httpUploadMock, entitiesMock } = setup(Promise.resolve({}))
const mockedUpload = {
sys: {
id: 'some_random_id'
}
}
httpUploadMock.post.returns(Promise.resolve({
data: mockedUpload
}))

return api.createUpload({
contentType: 'image/svg',
fileName: 'filename.svg',
file: '<svg><path fill="red" d="M50 50h150v50H50z"/></svg>'
})
.then(() => {
t.equals(httpUploadMock.post.args[0][1], '<svg><path fill="red" d="M50 50h150v50H50z"/></svg>', 'uploads file to upload endpoint')
t.deepEqual(entitiesMock.upload.wrapUpload.args[0][1], mockedUpload, 'wrapUpload was called with correct raw upload object')
})
})

test('API call createAssetFromFiles with invalid data', (t) => {
const { api } = setup(Promise.resolve({}))
return t.shouldFail(api.createAssetFromFiles({
Expand Down
35 changes: 35 additions & 0 deletions test/unit/entities/upload-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import test from 'tape'
import { cloneMock } from '../mocks/entities'
import setupHttpMock from '../mocks/http'
import { wrapUpload } from '../../../lib/entities/upload'
import {
entityWrappedTest,
entityDeleteTest,
failingActionTest
} from '../test-creators/instance-entity-methods'

function setup (promise) {
return {
httpMock: setupHttpMock(promise),
entityMock: cloneMock('upload')
}
}

test('Upload is wrapped', (t) => {
entityWrappedTest(t, setup, {
wrapperMethod: wrapUpload
})
})

test('Upload delete', (t) => {
return entityDeleteTest(t, setup, {
wrapperMethod: wrapUpload
})
})

test('Upload delete fails', (t) => {
return failingActionTest(t, setup, {
wrapperMethod: wrapUpload,
actionMethod: 'delete'
})
})
16 changes: 14 additions & 2 deletions test/unit/mocks/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ const assetWithFilesMock = {
}
}

const uploadMock = {
sys: assign(cloneDeep(sysMock), {
type: 'Upload',
id: 'some_random_id'
})
}

const localeMock = {
sys: assign(cloneDeep(sysMock), {
type: 'Locale'
Expand Down Expand Up @@ -167,7 +174,8 @@ const mocks = {
spaceMembership: spaceMembershipMock,
role: roleMock,
apiKey: apiKeyMock,
error: errorMock
error: errorMock,
upload: uploadMock
}

function cloneMock (name) {
Expand Down Expand Up @@ -223,6 +231,9 @@ function setupEntitiesMock (rewiredModuleApi) {
},
editorInterface: {
wrapEditorInterface: sinon.stub()
},
upload: {
wrapUpload: sinon.stub()
}
}
rewiredModuleApi.__Rewire__('entities', entitiesMock)
Expand All @@ -247,5 +258,6 @@ export {
errorMock,
cloneMock,
mockCollection,
setupEntitiesMock
setupEntitiesMock,
uploadMock
}

0 comments on commit a4805b4

Please sign in to comment.