test(backend-gitlab): cover gitlab backend integration (#1968)
This commit is contained in:
parent
9ebb17c944
commit
321ca9b0ed
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
'\\.js$': '<rootDir>/custom-preprocessor.js',
|
'\\.js$': '<rootDir>/custom-preprocessor.js',
|
||||||
},
|
},
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
'netlify-cms-lib-auth': '<rootDir>/packages/netlify-cms-lib-auth/src/index.js',
|
||||||
'netlify-cms-lib-util': '<rootDir>/packages/netlify-cms-lib-util/src/index.js',
|
'netlify-cms-lib-util': '<rootDir>/packages/netlify-cms-lib-util/src/index.js',
|
||||||
'netlify-cms-ui-default': '<rootDir>/packages/netlify-cms-ui-default/src/index.js',
|
'netlify-cms-ui-default': '<rootDir>/packages/netlify-cms-ui-default/src/index.js',
|
||||||
},
|
},
|
||||||
|
@ -101,6 +101,8 @@
|
|||||||
"jest-dom": "^3.1.3",
|
"jest-dom": "^3.1.3",
|
||||||
"jest-emotion": "^10.0.9",
|
"jest-emotion": "^10.0.9",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
|
"nock": "^10.0.4",
|
||||||
|
"node-fetch": "^2.3.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "1.17.1",
|
"prettier": "1.17.1",
|
||||||
"react-test-renderer": "^16.8.4",
|
"react-test-renderer": "^16.8.4",
|
||||||
|
488
packages/netlify-cms-backend-gitlab/src/__tests__/gitlab.spec.js
Normal file
488
packages/netlify-cms-backend-gitlab/src/__tests__/gitlab.spec.js
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
jest.mock('netlify-cms-core/src/backend');
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import { partial } from 'lodash';
|
||||||
|
import { oneLine, stripIndent } from 'common-tags';
|
||||||
|
import nock from 'nock';
|
||||||
|
import { Cursor } from 'netlify-cms-lib-util';
|
||||||
|
import Gitlab from '../implementation';
|
||||||
|
import AuthenticationPage from '../AuthenticationPage';
|
||||||
|
|
||||||
|
const { Backend, LocalStorageAuthStore } = jest.requireActual('netlify-cms-core/src/backend');
|
||||||
|
|
||||||
|
const generateEntries = (path, length) => {
|
||||||
|
const entries = Array.from({ length }, (val, idx) => {
|
||||||
|
const count = idx + 1;
|
||||||
|
const id = `00${count}`.slice(-3);
|
||||||
|
const fileName = `test${id}.md`;
|
||||||
|
return { id, fileName, filePath: `${path}/${fileName}` };
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tree: entries.map(({ id, fileName, filePath }) => ({
|
||||||
|
id: `d8345753a1d935fa47a26317a503e73e1192d${id}`,
|
||||||
|
name: fileName,
|
||||||
|
type: 'blob',
|
||||||
|
path: filePath,
|
||||||
|
mode: '100644',
|
||||||
|
})),
|
||||||
|
files: entries.reduce(
|
||||||
|
(acc, { id, filePath }) => ({
|
||||||
|
...acc,
|
||||||
|
[filePath]: stripIndent`
|
||||||
|
---
|
||||||
|
title: test ${id}
|
||||||
|
---
|
||||||
|
# test ${id}
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const manyEntries = generateEntries('many-entries', 500);
|
||||||
|
|
||||||
|
const mockRepo = {
|
||||||
|
tree: {
|
||||||
|
'/': [
|
||||||
|
{
|
||||||
|
id: '5d0620ebdbc92068a3e866866e928cc373f18429',
|
||||||
|
name: 'content',
|
||||||
|
type: 'tree',
|
||||||
|
path: 'content',
|
||||||
|
mode: '040000',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
id: 'b1a200e48be54fde12b636f9563d659d44c206a5',
|
||||||
|
name: 'test1.md',
|
||||||
|
type: 'blob',
|
||||||
|
path: 'content/test1.md',
|
||||||
|
mode: '100644',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'd8345753a1d935fa47a26317a503e73e1192d623',
|
||||||
|
name: 'test2.md',
|
||||||
|
type: 'blob',
|
||||||
|
path: 'content/test2.md',
|
||||||
|
mode: '100644',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'many-entries': manyEntries.tree,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
'content/test1.md': stripIndent`
|
||||||
|
---
|
||||||
|
title: test
|
||||||
|
---
|
||||||
|
# test
|
||||||
|
`,
|
||||||
|
'content/test2.md': stripIndent`
|
||||||
|
---
|
||||||
|
title: test2
|
||||||
|
---
|
||||||
|
# test 2
|
||||||
|
`,
|
||||||
|
...manyEntries.files,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resp = {
|
||||||
|
user: {
|
||||||
|
success: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
success: {
|
||||||
|
permissions: {
|
||||||
|
project_access: {
|
||||||
|
access_level: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
permissions: {
|
||||||
|
project_access: {
|
||||||
|
access_level: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('gitlab backend', () => {
|
||||||
|
let authStore;
|
||||||
|
let backend;
|
||||||
|
const repo = 'foo/bar';
|
||||||
|
const defaultConfig = {
|
||||||
|
backend: {
|
||||||
|
name: 'gitlab',
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collectionContentConfig = {
|
||||||
|
name: 'foo',
|
||||||
|
folder: 'content',
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
// TODO: folder_based_collection is an internal string, we should not
|
||||||
|
// be depending on it here
|
||||||
|
type: 'folder_based_collection',
|
||||||
|
};
|
||||||
|
const collectionManyEntriesConfig = {
|
||||||
|
name: 'foo',
|
||||||
|
folder: 'many-entries',
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
// TODO: folder_based_collection is an internal string, we should not
|
||||||
|
// be depending on it here
|
||||||
|
type: 'folder_based_collection',
|
||||||
|
};
|
||||||
|
const collectionFilesConfig = {
|
||||||
|
name: 'foo',
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
label: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
file: 'content/test1.md',
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'bar',
|
||||||
|
name: 'bar',
|
||||||
|
file: 'content/test2.md',
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'file_based_collection',
|
||||||
|
};
|
||||||
|
const mockCredentials = { token: 'MOCK_TOKEN' };
|
||||||
|
const expectedRepo = encodeURIComponent(repo);
|
||||||
|
const expectedRepoUrl = `/projects/${expectedRepo}`;
|
||||||
|
|
||||||
|
function resolveBackend(config = {}) {
|
||||||
|
authStore = new LocalStorageAuthStore();
|
||||||
|
return new Backend(
|
||||||
|
{
|
||||||
|
init: (...args) => new Gitlab(...args),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
backendName: 'gitlab',
|
||||||
|
config: fromJS(config),
|
||||||
|
authStore,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockApi(backend) {
|
||||||
|
return nock(backend.implementation.api_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
function interceptAuth(backend, { userResponse, projectResponse } = {}) {
|
||||||
|
const api = mockApi(backend);
|
||||||
|
api
|
||||||
|
.get('/user')
|
||||||
|
.query(true)
|
||||||
|
.reply(200, userResponse || resp.user.success);
|
||||||
|
|
||||||
|
api
|
||||||
|
.get(expectedRepoUrl)
|
||||||
|
.query(true)
|
||||||
|
.reply(200, projectResponse || resp.project.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseQuery(uri) {
|
||||||
|
const query = uri.split('?')[1];
|
||||||
|
if (!query) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return query.split('&').reduce((acc, q) => {
|
||||||
|
const [key, value] = q.split('=');
|
||||||
|
acc[key] = value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHeaders(backend, { basePath, path, page, perPage, pageCount, totalCount }) {
|
||||||
|
const pageNum = parseInt(page, 10);
|
||||||
|
const pageCountNum = parseInt(pageCount, 10);
|
||||||
|
const url = `${backend.implementation.api_root}${basePath}`;
|
||||||
|
const link = linkPage =>
|
||||||
|
`<${url}?id=${expectedRepo}&page=${linkPage}&path=${path}&per_page=${perPage}&recursive=false>`;
|
||||||
|
|
||||||
|
const linkHeader = oneLine`
|
||||||
|
${link(1)}; rel="first",
|
||||||
|
${link(pageCount)}; rel="last",
|
||||||
|
${pageNum === 1 ? '' : `${link(pageNum - 1)}; rel="prev",`}
|
||||||
|
${pageNum === pageCountNum ? '' : `${link(pageNum + 1)}; rel="next",`}
|
||||||
|
`.slice(0, -1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
'X-Page': page,
|
||||||
|
'X-Total-Pages': pageCount,
|
||||||
|
'X-Per-Page': perPage,
|
||||||
|
'X-Total': totalCount,
|
||||||
|
Link: linkHeader,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function interceptCollection(
|
||||||
|
backend,
|
||||||
|
collection,
|
||||||
|
{ verb = 'get', repeat = 1, page: expectedPage } = {},
|
||||||
|
) {
|
||||||
|
const api = mockApi(backend);
|
||||||
|
const url = `${expectedRepoUrl}/repository/tree`;
|
||||||
|
const { folder } = collection;
|
||||||
|
const tree = mockRepo.tree[folder];
|
||||||
|
api[verb](url)
|
||||||
|
.query(({ path, page }) => {
|
||||||
|
if (path !== folder) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (expectedPage && page && parseInt(page, 10) !== parseInt(expectedPage, 10)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.times(repeat)
|
||||||
|
.reply(uri => {
|
||||||
|
const { page = 1, per_page = 20 } = parseQuery(uri);
|
||||||
|
const pageCount = tree.length <= per_page ? 1 : Math.round(tree.length / per_page);
|
||||||
|
const pageLastIndex = page * per_page;
|
||||||
|
const pageFirstIndex = pageLastIndex - per_page;
|
||||||
|
const resp = tree.slice(pageFirstIndex, pageLastIndex);
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
verb === 'head' ? null : resp,
|
||||||
|
createHeaders(backend, {
|
||||||
|
basePath: url,
|
||||||
|
path: folder,
|
||||||
|
page,
|
||||||
|
perPage: per_page,
|
||||||
|
pageCount,
|
||||||
|
totalCount: tree.length,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function interceptFiles(backend, path) {
|
||||||
|
const api = mockApi(backend);
|
||||||
|
const url = `${expectedRepoUrl}/repository/files/${encodeURIComponent(path)}/raw`;
|
||||||
|
api
|
||||||
|
.get(url)
|
||||||
|
.query(true)
|
||||||
|
.reply(200, mockRepo.files[path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sharedSetup() {
|
||||||
|
beforeEach(async () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend);
|
||||||
|
await backend.authenticate(mockCredentials);
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { verb: 'head' });
|
||||||
|
interceptCollection(backend, collectionContentConfig, { verb: 'head' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('throws if configuration requires editorial workflow', () => {
|
||||||
|
const resolveBackendWithWorkflow = partial(resolveBackend, {
|
||||||
|
...defaultConfig,
|
||||||
|
publish_mode: 'editorial_workflow',
|
||||||
|
});
|
||||||
|
expect(resolveBackendWithWorkflow).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"The GitLab backend does not support the Editorial Workflow."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if configuration does not include repo', () => {
|
||||||
|
expect(resolveBackend).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"The GitLab backend needs a \\"repo\\" in the backend configuration."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authComponent', () => {
|
||||||
|
it('returns authentication page component', () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
expect(backend.authComponent()).toEqual(AuthenticationPage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authenticate', () => {
|
||||||
|
it('throws if user does not have access to project', async () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend, { projectResponse: resp.project.readOnly });
|
||||||
|
await expect(
|
||||||
|
backend.authenticate(mockCredentials),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Your GitLab user account does not have access to this repo."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stores and returns user object on success', async () => {
|
||||||
|
const backendName = defaultConfig.backend.name;
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend);
|
||||||
|
const user = await backend.authenticate(mockCredentials);
|
||||||
|
expect(authStore.retrieve()).toEqual(user);
|
||||||
|
expect(user).toEqual({ ...resp.user.success, ...mockCredentials, backendName });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('currentUser', () => {
|
||||||
|
it('returns null if no user', async () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
const user = await backend.currentUser();
|
||||||
|
expect(user).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the stored user if exists', async () => {
|
||||||
|
const backendName = defaultConfig.backend.name;
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend);
|
||||||
|
await backend.authenticate(mockCredentials);
|
||||||
|
const user = await backend.currentUser();
|
||||||
|
expect(user).toEqual({ ...resp.user.success, ...mockCredentials, backendName });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getToken', () => {
|
||||||
|
it('returns the token for the current user', async () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend);
|
||||||
|
await backend.authenticate(mockCredentials);
|
||||||
|
const token = await backend.getToken();
|
||||||
|
expect(token).toEqual(mockCredentials.token);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logout', () => {
|
||||||
|
it('sets token to null', async () => {
|
||||||
|
backend = resolveBackend(defaultConfig);
|
||||||
|
interceptAuth(backend);
|
||||||
|
await backend.authenticate(mockCredentials);
|
||||||
|
await backend.logout();
|
||||||
|
const token = await backend.getToken();
|
||||||
|
expect(token).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getEntry', () => {
|
||||||
|
sharedSetup();
|
||||||
|
|
||||||
|
it('returns an entry from folder collection', async () => {
|
||||||
|
const entryTree = mockRepo.tree[collectionContentConfig.folder][0];
|
||||||
|
const slug = entryTree.path
|
||||||
|
.split('/')
|
||||||
|
.pop()
|
||||||
|
.replace('.md', '');
|
||||||
|
|
||||||
|
interceptFiles(backend, entryTree.path);
|
||||||
|
interceptCollection(backend, collectionContentConfig);
|
||||||
|
const entry = await backend.getEntry(fromJS(collectionContentConfig), slug);
|
||||||
|
|
||||||
|
expect(entry).toEqual(expect.objectContaining({ path: entryTree.path }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('listEntries', () => {
|
||||||
|
sharedSetup();
|
||||||
|
|
||||||
|
it('returns entries from folder collection', async () => {
|
||||||
|
const tree = mockRepo.tree[collectionContentConfig.folder];
|
||||||
|
tree.forEach(file => interceptFiles(backend, file.path));
|
||||||
|
|
||||||
|
interceptCollection(backend, collectionContentConfig);
|
||||||
|
const entries = await backend.listEntries(fromJS(collectionContentConfig));
|
||||||
|
|
||||||
|
expect(entries).toEqual({
|
||||||
|
cursor: expect.any(Cursor),
|
||||||
|
entries: expect.arrayContaining(
|
||||||
|
tree.map(file => expect.objectContaining({ path: file.path })),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
expect(entries.entries).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns all entries from folder collection', async () => {
|
||||||
|
const tree = mockRepo.tree[collectionManyEntriesConfig.folder];
|
||||||
|
tree.forEach(file => interceptFiles(backend, file.path));
|
||||||
|
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { repeat: 5 });
|
||||||
|
const entries = await backend.listAllEntries(fromJS(collectionManyEntriesConfig));
|
||||||
|
|
||||||
|
expect(entries).toEqual(
|
||||||
|
expect.arrayContaining(tree.map(file => expect.objectContaining({ path: file.path }))),
|
||||||
|
);
|
||||||
|
expect(entries).toHaveLength(500);
|
||||||
|
}, 7000);
|
||||||
|
|
||||||
|
it('returns entries from file collection', async () => {
|
||||||
|
const { files } = collectionFilesConfig;
|
||||||
|
files.forEach(file => interceptFiles(backend, file.file));
|
||||||
|
const entries = await backend.listEntries(fromJS(collectionFilesConfig));
|
||||||
|
|
||||||
|
expect(entries).toEqual({
|
||||||
|
cursor: expect.any(Cursor),
|
||||||
|
entries: expect.arrayContaining(
|
||||||
|
files.map(file => expect.objectContaining({ path: file.file })),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
expect(entries.entries).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns last page from paginated folder collection tree', async () => {
|
||||||
|
const tree = mockRepo.tree[collectionManyEntriesConfig.folder];
|
||||||
|
const pageTree = tree.slice(-20);
|
||||||
|
pageTree.forEach(file => interceptFiles(backend, file.path));
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { page: 25 });
|
||||||
|
const entries = await backend.listEntries(fromJS(collectionManyEntriesConfig));
|
||||||
|
|
||||||
|
expect(entries.entries).toEqual(
|
||||||
|
expect.arrayContaining(pageTree.map(file => expect.objectContaining({ path: file.path }))),
|
||||||
|
);
|
||||||
|
expect(entries.entries).toHaveLength(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('traverseCursor', () => {
|
||||||
|
sharedSetup();
|
||||||
|
|
||||||
|
it('returns complete last page of paginated tree', async () => {
|
||||||
|
const tree = mockRepo.tree[collectionManyEntriesConfig.folder];
|
||||||
|
tree.slice(-20).forEach(file => interceptFiles(backend, file.path));
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { page: 25 });
|
||||||
|
const entries = await backend.listEntries(fromJS(collectionManyEntriesConfig));
|
||||||
|
|
||||||
|
const nextPageTree = tree.slice(-40, -20);
|
||||||
|
nextPageTree.forEach(file => interceptFiles(backend, file.path));
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { page: 24 });
|
||||||
|
const nextPage = await backend.traverseCursor(entries.cursor, 'next');
|
||||||
|
|
||||||
|
expect(nextPage.entries).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
nextPageTree.map(file => expect.objectContaining({ path: file.path })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(nextPage.entries).toHaveLength(20);
|
||||||
|
|
||||||
|
const prevPageTree = tree.slice(-20);
|
||||||
|
interceptCollection(backend, collectionManyEntriesConfig, { page: 25 });
|
||||||
|
const prevPage = await backend.traverseCursor(nextPage.cursor, 'prev');
|
||||||
|
expect(prevPage.entries).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
prevPageTree.map(file => expect.objectContaining({ path: file.path })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(prevPage.entries).toHaveLength(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
authStore.logout();
|
||||||
|
backend = null;
|
||||||
|
expect(authStore.retrieve()).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
@ -32,7 +32,7 @@ import {
|
|||||||
dateParsers,
|
dateParsers,
|
||||||
} from 'Lib/stringTemplate';
|
} from 'Lib/stringTemplate';
|
||||||
|
|
||||||
class LocalStorageAuthStore {
|
export class LocalStorageAuthStore {
|
||||||
storageKey = 'netlify-cms-user';
|
storageKey = 'netlify-cms-user';
|
||||||
|
|
||||||
retrieve() {
|
retrieve() {
|
||||||
@ -200,7 +200,7 @@ function createPreviewUrl(baseUrl, collection, slug, slugConfig, entry) {
|
|||||||
return `${basePath}/${previewPath}`;
|
return `${basePath}/${previewPath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Backend {
|
export class Backend {
|
||||||
constructor(implementation, { backendName, authStore = null, config } = {}) {
|
constructor(implementation, { backendName, authStore = null, config } = {}) {
|
||||||
// We can't reliably run this on exit, so we do cleanup on load.
|
// We can't reliably run this on exit, so we do cleanup on load.
|
||||||
this.deleteAnonymousBackup();
|
this.deleteAnonymousBackup();
|
||||||
@ -224,10 +224,10 @@ class Backend {
|
|||||||
const stored = this.authStore && this.authStore.retrieve();
|
const stored = this.authStore && this.authStore.retrieve();
|
||||||
if (stored && stored.backendName === this.backendName) {
|
if (stored && stored.backendName === this.backendName) {
|
||||||
return Promise.resolve(this.implementation.restoreUser(stored)).then(user => {
|
return Promise.resolve(this.implementation.restoreUser(stored)).then(user => {
|
||||||
const newUser = { ...user, backendName: this.backendName };
|
this.user = { ...user, backendName: this.backendName };
|
||||||
// return confirmed/rehydrated user object instead of stored
|
// return confirmed/rehydrated user object instead of stored
|
||||||
this.authStore.store(newUser);
|
this.authStore.store(this.user);
|
||||||
return newUser;
|
return this.user;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
@ -236,9 +236,9 @@ class Backend {
|
|||||||
updateUserCredentials = updatedCredentials => {
|
updateUserCredentials = updatedCredentials => {
|
||||||
const storedUser = this.authStore && this.authStore.retrieve();
|
const storedUser = this.authStore && this.authStore.retrieve();
|
||||||
if (storedUser && storedUser.backendName === this.backendName) {
|
if (storedUser && storedUser.backendName === this.backendName) {
|
||||||
const newUser = { ...storedUser, ...updatedCredentials };
|
this.user = { ...storedUser, ...updatedCredentials };
|
||||||
this.authStore.store(newUser);
|
this.authStore.store(this.user);
|
||||||
return newUser;
|
return this.user;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -248,16 +248,17 @@ class Backend {
|
|||||||
|
|
||||||
authenticate(credentials) {
|
authenticate(credentials) {
|
||||||
return this.implementation.authenticate(credentials).then(user => {
|
return this.implementation.authenticate(credentials).then(user => {
|
||||||
const newUser = { ...user, backendName: this.backendName };
|
this.user = { ...user, backendName: this.backendName };
|
||||||
if (this.authStore) {
|
if (this.authStore) {
|
||||||
this.authStore.store(newUser);
|
this.authStore.store(this.user);
|
||||||
}
|
}
|
||||||
return newUser;
|
return this.user;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
return Promise.resolve(this.implementation.logout()).then(() => {
|
return Promise.resolve(this.implementation.logout()).then(() => {
|
||||||
|
this.user = null;
|
||||||
if (this.authStore) {
|
if (this.authStore) {
|
||||||
this.authStore.logout();
|
this.authStore.logout();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/* eslint-disable emotion/no-vanilla */
|
/* eslint-disable emotion/no-vanilla */
|
||||||
|
import fetch from 'node-fetch';
|
||||||
import * as emotion from 'emotion';
|
import * as emotion from 'emotion';
|
||||||
import { createSerializer } from 'jest-emotion';
|
import { createSerializer } from 'jest-emotion';
|
||||||
|
|
||||||
|
window.fetch = fetch;
|
||||||
expect.addSnapshotSerializer(createSerializer(emotion));
|
expect.addSnapshotSerializer(createSerializer(emotion));
|
||||||
|
68
yarn.lock
68
yarn.lock
@ -2520,6 +2520,11 @@ assert@^1.1.1:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
util "0.10.3"
|
util "0.10.3"
|
||||||
|
|
||||||
|
assertion-error@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||||
|
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||||
|
|
||||||
assign-symbols@^1.0.0:
|
assign-symbols@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
@ -3288,6 +3293,18 @@ ccount@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386"
|
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386"
|
||||||
integrity sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==
|
integrity sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==
|
||||||
|
|
||||||
|
chai@^4.1.2:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
|
||||||
|
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
|
||||||
|
dependencies:
|
||||||
|
assertion-error "^1.1.0"
|
||||||
|
check-error "^1.0.2"
|
||||||
|
deep-eql "^3.0.1"
|
||||||
|
get-func-name "^2.0.0"
|
||||||
|
pathval "^1.1.0"
|
||||||
|
type-detect "^4.0.5"
|
||||||
|
|
||||||
chain-function@^1.0.0:
|
chain-function@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
|
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
|
||||||
@ -3338,6 +3355,11 @@ chardet@^0.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||||
|
|
||||||
|
check-error@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||||
|
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
|
||||||
|
|
||||||
check-more-types@2.24.0:
|
check-more-types@2.24.0:
|
||||||
version "2.24.0"
|
version "2.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||||
@ -4218,7 +4240,14 @@ dedent@^0.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
||||||
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
|
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
|
||||||
|
|
||||||
deep-equal@^1.0.1:
|
deep-eql@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||||
|
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
|
||||||
|
dependencies:
|
||||||
|
type-detect "^4.0.0"
|
||||||
|
|
||||||
|
deep-equal@^1.0.0, deep-equal@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||||
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
|
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
|
||||||
@ -5644,6 +5673,11 @@ get-document@1:
|
|||||||
resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b"
|
resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b"
|
||||||
integrity sha1-SCG85m8cJMsDMWAr5strEsTwHEs=
|
integrity sha1-SCG85m8cJMsDMWAr5strEsTwHEs=
|
||||||
|
|
||||||
|
get-func-name@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||||
|
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
|
||||||
|
|
||||||
get-pkg-repo@^1.0.0:
|
get-pkg-repo@^1.0.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
|
resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
|
||||||
@ -8528,6 +8562,21 @@ nice-try@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
|
nock@^10.0.4:
|
||||||
|
version "10.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/nock/-/nock-10.0.6.tgz#e6d90ee7a68b8cfc2ab7f6127e7d99aa7d13d111"
|
||||||
|
integrity sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==
|
||||||
|
dependencies:
|
||||||
|
chai "^4.1.2"
|
||||||
|
debug "^4.1.0"
|
||||||
|
deep-equal "^1.0.0"
|
||||||
|
json-stringify-safe "^5.0.1"
|
||||||
|
lodash "^4.17.5"
|
||||||
|
mkdirp "^0.5.0"
|
||||||
|
propagate "^1.0.0"
|
||||||
|
qs "^6.5.1"
|
||||||
|
semver "^5.5.0"
|
||||||
|
|
||||||
node-addon-api@^1.6.0:
|
node-addon-api@^1.6.0:
|
||||||
version "1.6.3"
|
version "1.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.3.tgz#3998d4593e2dca2ea82114670a4eb003386a9fe1"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.3.tgz#3998d4593e2dca2ea82114670a4eb003386a9fe1"
|
||||||
@ -9358,6 +9407,11 @@ path-type@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
|
|
||||||
|
pathval@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
|
||||||
|
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
|
||||||
|
|
||||||
pause-stream@0.0.11:
|
pause-stream@0.0.11:
|
||||||
version "0.0.11"
|
version "0.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
|
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
|
||||||
@ -9712,6 +9766,11 @@ prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6,
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.8.1"
|
react-is "^16.8.1"
|
||||||
|
|
||||||
|
propagate@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709"
|
||||||
|
integrity sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=
|
||||||
|
|
||||||
property-information@^3.0.0, property-information@^3.1.0:
|
property-information@^3.0.0, property-information@^3.1.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/property-information/-/property-information-3.2.0.tgz#fd1483c8fbac61808f5fe359e7693a1f48a58331"
|
resolved "https://registry.yarnpkg.com/property-information/-/property-information-3.2.0.tgz#fd1483c8fbac61808f5fe359e7693a1f48a58331"
|
||||||
@ -9826,7 +9885,7 @@ q@^1.1.2, q@^1.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||||
|
|
||||||
qs@6.7.0:
|
qs@6.7.0, qs@^6.5.1:
|
||||||
version "6.7.0"
|
version "6.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
@ -12260,6 +12319,11 @@ type-check@~0.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
|
|
||||||
|
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||||
|
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||||
|
|
||||||
type-fest@^0.4.1:
|
type-fest@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user