diff --git a/jest.config.js b/jest.config.js index 0acaed82..92a50e69 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ module.exports = { moduleFileExtensions: ['js', 'jsx', 'json'], - setupFilesAfterEnv: ['enzyme.setup.js'], + setupFilesAfterEnv: ['enzyme.setup.js', 'jest.setup.js'], testPathIgnorePatterns: ['@bitwarden/jslib', 'transpiled/'] } diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 00000000..395cea49 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ + +const ignoreOnConditions = (originalWarn, ignoreConditions) => { + return function(...args) { + const msg = args[0] + if (ignoreConditions.some(condition => condition(msg))) { + return + } + originalWarn.apply(this, args) + } +} + +const callAndThrow = (fn, errorMessage) => { + return function() { + fn.apply(this, arguments) + throw new Error(errorMessage) + } +} + +const ignoredErrors = { + 'mocked error': { + reason: 'Mocked error', + matcher: message => message.includes('mock error') + } +} + +console.error = ignoreOnConditions( + callAndThrow( + console.error, + 'console.error should not be called during tests' + ), + Object.values(ignoredErrors).map(x => x.matcher) +) diff --git a/package.json b/package.json index c99abadc..46f2d4a7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "babel-plugin-mock-imports": "^1.0.2", "babel-plugin-rewire": "1.2.0", "babel-preset-cozy-app": "^1.6.0", - "cozy-client": "^8.5.1", + "cozy-client": "^8.8.0", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "enzyme-to-json": "3.4.0", @@ -78,6 +78,6 @@ "zxcvbn": "^4.4.2" }, "peerDependencies": { - "cozy-client": "^8.5.1" + "cozy-client": "^8.8.0" } } diff --git a/src/components/VaultUnlocker.jsx b/src/components/VaultUnlocker.jsx index 2430171f..956beea0 100644 --- a/src/components/VaultUnlocker.jsx +++ b/src/components/VaultUnlocker.jsx @@ -6,7 +6,7 @@ import localesEn from '../locales/en.json' import localesFr from '../locales/fr.json' import Spinner from 'cozy-ui/transpiled/react/Spinner' import { withClient } from 'cozy-client' -import { checkHasCiphers } from '../utils' +import { checkHasCiphers, checkHasInstalledExtension } from '../utils' const locales = { en: localesEn, @@ -27,8 +27,11 @@ const VaultUnlocker = ({ useEffect(() => { const checkShouldUnlock = async () => { - const hasCiphers = await checkHasCiphers(cozyClient) - const shouldUnlock = hasCiphers + let shouldUnlock = await checkHasCiphers(cozyClient) + + if (!shouldUnlock) { + shouldUnlock = await checkHasInstalledExtension(cozyClient) + } setShouldUnlock(shouldUnlock) setIsChecking(false) diff --git a/src/utils.js b/src/utils.js index 3f2357c2..d7ceb58c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,5 @@ const CIPHERS_DOCTYPE = 'com.bitwarden.ciphers' +const SETTINGS_DOCTYPE = 'io.cozy.settings' const isForbiddenError = rawError => { return rawError.message.match(/code=403/) @@ -13,13 +14,38 @@ export const checkHasCiphers = async cozyClient => { return ciphers.length > 0 } catch (err) { /* eslint-disable no-console */ - console.error('Error while fetching ciphers:') if (isForbiddenError(err)) { console.error( `Your app must have the GET permission on the ${CIPHERS_DOCTYPE} doctype.` ) } else { - console.error(err) + console.error(err.message) + } + /* eslint-enable no-console */ + + return false + } +} + +export const checkHasInstalledExtension = async cozyClient => { + try { + const { rows: docs } = await cozyClient + .getStackClient() + .fetchJSON('GET', `/data/${SETTINGS_DOCTYPE}/_normal_docs`) + + const [bitwardenSettings] = docs.filter( + doc => doc._id === 'io.cozy.settings.bitwarden' + ) + + return bitwardenSettings && bitwardenSettings.extension_installed + } catch (err) { + /* eslint-disable no-console */ + if (isForbiddenError(err)) { + console.error( + `Your app must have the GET permission on the ${SETTINGS_DOCTYPE} doctype.` + ) + } else { + console.error(err.message) } /* eslint-enable no-console */ diff --git a/src/utils.spec.js b/src/utils.spec.js new file mode 100644 index 00000000..3a913e55 --- /dev/null +++ b/src/utils.spec.js @@ -0,0 +1,110 @@ +import { checkHasCiphers, checkHasInstalledExtension } from './utils' +import { createMockClient } from 'cozy-client/dist/mock' + +describe('checkHasCiphers', () => { + describe('when there are ciphers', () => { + let client + + beforeEach(() => { + client = createMockClient({ + remote: { + 'com.bitwarden.ciphers': [{ _id: 'cipher1' }, { _id: 'cipher2' }] + } + }) + }) + + it('should return true', async () => { + const hasCiphers = await checkHasCiphers(client) + + expect(hasCiphers).toBe(true) + }) + }) + + describe('when there is no cipher', () => { + let client + + beforeEach(() => { + client = createMockClient({ + remote: { + 'com.bitwarden.ciphers': [] + } + }) + }) + + it('should return false', async () => { + const hasCiphers = await checkHasCiphers(client) + + expect(hasCiphers).toBe(false) + }) + }) + + describe('when there is an error while fetching ciphers', () => { + let client + + beforeEach(() => { + client = createMockClient({}) + client.query = jest.fn().mockRejectedValue({ message: 'mock error' }) + }) + + it('should return false', async () => { + const hasCiphers = await checkHasCiphers(client) + + expect(hasCiphers).toBe(false) + }) + }) +}) + +describe('checkHasInstalledExtension', () => { + describe('when the extension has been installed', () => { + let client + + beforeEach(() => { + client = createMockClient({}) + client.stackClient.fetchJSON = jest.fn().mockResolvedValue({ + rows: [{ _id: 'io.cozy.settings.bitwarden', extension_installed: true }] + }) + }) + + it('should return true', async () => { + const hasInstalledExtension = await checkHasInstalledExtension(client) + + expect(hasInstalledExtension).toBe(true) + }) + }) + + describe('when the extension has not been installed', () => { + let client + + beforeEach(() => { + client = createMockClient({}) + client.stackClient.fetchJSON = jest.fn().mockResolvedValue({ + rows: [ + { _id: 'io.cozy.settings.bitwarden', extension_installed: false } + ] + }) + }) + + it('should return false', async () => { + const hasInstalledExtension = await checkHasInstalledExtension(client) + + expect(hasInstalledExtension).toBe(false) + }) + }) + + describe('when there is an error while fetching settings', () => { + let client + + beforeEach(() => { + client = createMockClient({}) + client.stackClient.fetchJSON = jest + .fn() + .mockRejectedValue({ message: 'mock error' }) + }) + + it('should return false', async () => { + const hasInstalledExtension = await checkHasInstalledExtension(client) + + expect(hasInstalledExtension).toBe(false) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index cff19429..cd30bacf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2735,13 +2735,13 @@ cosmiconfig@^5.0.1: js-yaml "^3.13.1" parse-json "^4.0.0" -cozy-client@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-8.5.1.tgz#7303302a3f0241b965798c1a7239c3697f659b46" - integrity sha512-Ds+U2sGkQjK8nslcCIlysK/RAhCkjNX7jZdpun8KIZCRoryvmDYWND9Xprd+cB+MN4kueLPIZdasTHoFF4i38g== +cozy-client@^8.8.0: + version "8.10.1" + resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-8.10.1.tgz#d1663c915141274f5cf413c12f31e2cc936a963f" + integrity sha512-VYEx4GGfcSpL3MKcFcxV//PsklFDB3azwDJIIBmPmamLVY+1Axgwc8uTeweIZSetbtPZHXMQyogQ4qf9vq80fg== dependencies: cozy-device-helper "^1.7.3" - cozy-stack-client "^8.5.1" + cozy-stack-client "^8.8.0" lodash "^4.17.13" microee "^0.0.6" prop-types "^15.6.2" @@ -2765,10 +2765,10 @@ cozy-device-helper@^1.7.5: dependencies: lodash "4.17.15" -cozy-stack-client@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-8.5.1.tgz#a3f88eee07d168a0eafe70bc68f9333ed34c71a3" - integrity sha512-SZB1hma9Zh50FeTiCl9bFneUy2XNwo++W6x9zd70tfNsMlXt504iDaV8MCew2lvuVszK+HFzsq+hyByB0m2VlA== +cozy-stack-client@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-8.8.0.tgz#0bfbfcc3180c1eb6b1468a3d14b965b05c84c84d" + integrity sha512-Ltc/OjDN9EdlMioSsQM0zCw7lb+LZisjU0XeOaTK+1jH1AdWBdywdu1uUrxCSF/rStsqfBqleNFwPK5WySvPTw== dependencies: detect-node "^2.0.4" mime "^2.4.0"