-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from DopplerLabs/feature/tests
Feature/tests
- Loading branch information
Showing
8 changed files
with
2,007 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
"presets": ["node6", "stage-1"], | ||
"plugins": [ | ||
"espower", | ||
"add-module-exports" | ||
], | ||
"env": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
--- | ||
extends: [standard] | ||
extends: [standard, plugin:ava/recommended] | ||
plugins: [ava] | ||
rules: | ||
space-before-function-paren: ["error", "never"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ language: node_js | |
node_js: | ||
- '6' | ||
script: npm run build | ||
after_success: npm run ci:coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,39 @@ | ||
# parse-server-migrating-adapter | ||
Parse Server file adapter for migrating between different adapters | ||
[![Coverage Status](https://coveralls.io/repos/github/DopplerLabs/parse-server-migrating-adapter/badge.svg?branch=develop)](https://coveralls.io/github/DopplerLabs/parse-server-migrating-adapter?branch=develop) | ||
|
||
[![Build Status](https://travis-ci.org/DopplerLabs/parse-server-migrating-adapter.svg?branch=develop)](https://travis-ci.org/DopplerLabs/parse-server-migrating-adapter) | ||
|
||
Parse Server file adapter for migrating between different adapters. | ||
|
||
# Quick Start | ||
`$ npm install parse-server-migrating-adapter --save` | ||
|
||
In your parse server index: | ||
``` | ||
var ParseServer = require('parse-server').ParseServer; | ||
var MigratingAdapter = require('parse-server-migrating-adapter') | ||
var GridStoreAdapter = require('parse-server/lib/Adapters/Files/GridStoreAdapter').GridStoreAdapter | ||
var S3Adapter = require('parse-server-s3-adapter') | ||
var s3Adapter = new S3Adapter({ | ||
bucket: process.env.S3_BUCKET_NAME, | ||
accessKey: process.env.AWS_ACCESS_KEY, | ||
secretKey: process.env.AWS_SECRET, | ||
region: 'us-east-1' | ||
}) | ||
var fileAdapter = new MigratingAdapter(s3Adapter, [new GridStoreAdapter(process.env.DATABASE_URI)]) | ||
var api = new new ParseServer({ | ||
filesAdapter: fileAdapter | ||
}) | ||
``` | ||
|
||
# Implementation | ||
The adapter takes a main adapter, which is what is used to create any new files. It also takes a list of old adapters. When requesting a file, the main adapter is searched first, and then the old adapters are searched. If the file is found in an old adapter, it is stored on the main adapter. | ||
|
||
# Contributing | ||
This projects follows [standardjs](https://standardjs.com/). We also try to maintain 100% real test coverage. When submitting a PR, make sure that there is an accompanying test, and that `npm run build` is clean. | ||
|
||
# TODO | ||
* Support for file streaming |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import sinon from 'sinon' | ||
import test from 'ava' | ||
import MigratingAdapter from '../src' | ||
|
||
class StubAdapter { | ||
createFile(filename, data) { } | ||
deleteFile(filename) { } | ||
getFileData(filename) { } | ||
getFileLocation(config, filename) { } | ||
} | ||
|
||
test.beforeEach((t) => { | ||
t.context.sandbox = sinon.sandbox.create() | ||
t.context.mainAdapter = new StubAdapter() | ||
t.context.oldAdapters = [new StubAdapter(), new StubAdapter()] | ||
t.context.migratingAdapter = new MigratingAdapter( | ||
t.context.mainAdapter, t.context.oldAdapters) | ||
}) | ||
|
||
test.afterEach.always((t) => { | ||
t.context.sandbox.restore() | ||
}) | ||
|
||
test('constructor#requiresMainAdapter', t => { | ||
try { | ||
new MigratingAdapter(null, t.context.oldAdapters) // eslint-disable-line | ||
} catch (err) { | ||
t.is(err.message, 'Main adapter required') | ||
} | ||
}) | ||
|
||
test('constructor#requiresOldAdapter', t => { | ||
try { | ||
new MigratingAdapter(t.context.mainAdapter, null) // eslint-disable-line | ||
} catch (err) { | ||
t.is(err.message, 'At least one old adapter is required') | ||
} | ||
|
||
try { | ||
new MigratingAdapter(t.context.mainAdapter, []) // eslint-disable-line | ||
} catch (err) { | ||
t.is(err.message, 'At least one old adapter is required') | ||
} | ||
}) | ||
|
||
test('createFile#createsInMainAdapter', t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const createFileSpy = sandbox.spy(t.context.mainAdapter, 'createFile') | ||
|
||
migratingAdapter.createFile('foo', 'bar') | ||
t.true(createFileSpy.calledOnce) | ||
t.true(createFileSpy.calledWith('foo', 'bar')) | ||
}) | ||
|
||
test('getFileData#returnsFromMainAdapter', t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const mainGetFileStub = sandbox.stub(t.context.mainAdapter, 'getFileData').resolves('myData') | ||
const oldGetFileSpy = sandbox.spy(t.context.oldAdapters[0], 'getFileData') | ||
|
||
migratingAdapter.getFileData('foo') | ||
t.true(mainGetFileStub.calledOnce) | ||
t.true(mainGetFileStub.calledWith('foo')) | ||
t.true(oldGetFileSpy.notCalled) | ||
}) | ||
|
||
test('getFileData#getsFromOldAdapters', async t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
sandbox.stub(t.context.mainAdapter, 'getFileData').rejects() | ||
const oldStubs = [ | ||
sandbox.stub(t.context.oldAdapters[0], 'getFileData').rejects('wtf'), | ||
sandbox.stub(t.context.oldAdapters[1], 'getFileData').resolves('myData') | ||
] | ||
|
||
const fileData = await migratingAdapter.getFileData('foo') | ||
t.is(fileData, 'myData') | ||
|
||
for (const oldStub of oldStubs) { | ||
t.true(oldStub.calledOnce) | ||
t.true(oldStub.calledWith('foo')) | ||
} | ||
}) | ||
|
||
test('getFileData#storesToMainAdapter', async t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const createFileSpy = sandbox.spy(migratingAdapter, 'createFile') | ||
sandbox.stub(t.context.mainAdapter, 'getFileData').rejects() | ||
sandbox.stub(t.context.oldAdapters[0], 'getFileData').resolves('myData') | ||
sandbox.stub(t.context.oldAdapters[1], 'getFileData').rejects() | ||
|
||
await migratingAdapter.getFileData('foo') | ||
|
||
t.true(createFileSpy.calledOnce) | ||
t.true(createFileSpy.calledWith('foo', 'myData')) | ||
}) | ||
|
||
test('getFileData#throwsIfNotFound', async t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const rejectingError = { err: 'Not Found' } | ||
sandbox.stub(t.context.mainAdapter, 'getFileData').rejects(rejectingError) | ||
sandbox.stub(t.context.oldAdapters[0], 'getFileData').rejects() | ||
sandbox.stub(t.context.oldAdapters[1], 'getFileData').rejects() | ||
|
||
try { | ||
await migratingAdapter.getFileData('foo') | ||
} catch (err) { | ||
t.is(err, rejectingError) | ||
} | ||
}) | ||
|
||
test('getFileLocation#returnsMainAdapterLocation', t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
sandbox.stub(t.context.mainAdapter, 'getFileLocation').returns('mainFileLocation') | ||
|
||
t.is(migratingAdapter.getFileLocation({}, 'foo'), 'mainFileLocation') | ||
}) | ||
|
||
test('deleteFile#deletesFromAllAdapters', async t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const deleteStubs = [ | ||
sandbox.stub(t.context.mainAdapter, 'deleteFile').resolves(), | ||
sandbox.stub(t.context.oldAdapters[0], 'deleteFile').rejects(), | ||
sandbox.stub(t.context.oldAdapters[1], 'deleteFile').resolves() | ||
] | ||
|
||
await migratingAdapter.deleteFile('foo') | ||
for (const stub of deleteStubs) { | ||
t.true(stub.calledOnce) | ||
t.true(stub.calledWith('foo')) | ||
} | ||
}) | ||
|
||
test('deleteFile#throwsIfNotDeletedFromAnyAdapter', async t => { | ||
const migratingAdapter = t.context.migratingAdapter | ||
const sandbox = t.context.sandbox | ||
|
||
const rejectingError = { err: 'Not Found' } | ||
sandbox.stub(t.context.mainAdapter, 'deleteFile').rejects(rejectingError) | ||
sandbox.stub(t.context.oldAdapters[0], 'deleteFile').rejects() | ||
sandbox.stub(t.context.oldAdapters[1], 'deleteFile').rejects() | ||
|
||
try { | ||
await migratingAdapter.deleteFile('foo') | ||
} catch (err) { | ||
t.is(err, rejectingError) | ||
} | ||
}) |
Oops, something went wrong.