316 lines
11 KiB
JavaScript
316 lines
11 KiB
JavaScript
/* eslint-disable no-undef */
|
|
import * as Factory from './lib/factory.js'
|
|
chai.use(chaiAsPromised)
|
|
const expect = chai.expect
|
|
|
|
describe('features', () => {
|
|
let application
|
|
let email
|
|
let password
|
|
let midnightThemeFeature
|
|
let plusEditorFeature
|
|
let tagNestingFeature
|
|
let getUserFeatures
|
|
|
|
beforeEach(async function () {
|
|
application = await Factory.createInitAppWithFakeCrypto()
|
|
|
|
const now = new Date()
|
|
const tomorrow = now.setDate(now.getDate() + 1)
|
|
|
|
midnightThemeFeature = {
|
|
...GetFeatures().find((feature) => feature.identifier === FeatureIdentifier.MidnightTheme),
|
|
expires_at: tomorrow,
|
|
}
|
|
plusEditorFeature = {
|
|
...GetFeatures().find((feature) => feature.identifier === FeatureIdentifier.PlusEditor),
|
|
expires_at: tomorrow,
|
|
}
|
|
tagNestingFeature = {
|
|
...GetFeatures().find((feature) => feature.identifier === FeatureIdentifier.TagNesting),
|
|
expires_at: tomorrow,
|
|
}
|
|
|
|
sinon.spy(application.itemManager, 'createItem')
|
|
sinon.spy(application.itemManager, 'changeComponent')
|
|
sinon.spy(application.itemManager, 'setItemsToBeDeleted')
|
|
|
|
getUserFeatures = sinon.stub(application.apiService, 'getUserFeatures').callsFake(() => {
|
|
return Promise.resolve({
|
|
data: {
|
|
features: [midnightThemeFeature, plusEditorFeature, tagNestingFeature],
|
|
},
|
|
})
|
|
})
|
|
|
|
email = UuidGenerator.GenerateUuid()
|
|
password = UuidGenerator.GenerateUuid()
|
|
|
|
await Factory.registerUserToApplication({
|
|
application: application,
|
|
email: email,
|
|
password: password,
|
|
})
|
|
})
|
|
|
|
afterEach(async function () {
|
|
Factory.safeDeinit(application)
|
|
sinon.restore()
|
|
})
|
|
|
|
describe('new user roles received on api response meta', () => {
|
|
it('should save roles and features', async () => {
|
|
expect(application.featuresService.roles).to.have.lengthOf(1)
|
|
expect(application.featuresService.roles[0]).to.equal('CORE_USER')
|
|
|
|
expect(application.featuresService.features).to.have.lengthOf(3)
|
|
expect(application.featuresService.features[0]).to.containSubset(midnightThemeFeature)
|
|
expect(application.featuresService.features[1]).to.containSubset(plusEditorFeature)
|
|
|
|
const storedRoles = await application.getValue(StorageKey.UserRoles)
|
|
|
|
expect(storedRoles).to.have.lengthOf(1)
|
|
expect(storedRoles[0]).to.equal('CORE_USER')
|
|
|
|
const storedFeatures = await application.getValue(StorageKey.UserFeatures)
|
|
|
|
expect(storedFeatures).to.have.lengthOf(3)
|
|
expect(storedFeatures[0]).to.containSubset(midnightThemeFeature)
|
|
expect(storedFeatures[1]).to.containSubset(plusEditorFeature)
|
|
expect(storedFeatures[2]).to.containSubset(tagNestingFeature)
|
|
})
|
|
|
|
it('should fetch user features and create items for features with content type', async () => {
|
|
expect(application.apiService.getUserFeatures.callCount).to.equal(1)
|
|
expect(application.itemManager.createItem.callCount).to.equal(2)
|
|
const themeItems = application.items.getItems(ContentType.Theme)
|
|
const editorItems = application.items.getItems(ContentType.Component)
|
|
expect(themeItems).to.have.lengthOf(1)
|
|
expect(editorItems).to.have.lengthOf(1)
|
|
expect(themeItems[0].content).to.containSubset(
|
|
JSON.parse(
|
|
JSON.stringify({
|
|
name: midnightThemeFeature.name,
|
|
package_info: midnightThemeFeature,
|
|
valid_until: new Date(midnightThemeFeature.expires_at),
|
|
}),
|
|
),
|
|
)
|
|
expect(editorItems[0].content).to.containSubset(
|
|
JSON.parse(
|
|
JSON.stringify({
|
|
name: plusEditorFeature.name,
|
|
area: plusEditorFeature.area,
|
|
package_info: plusEditorFeature,
|
|
valid_until: new Date(midnightThemeFeature.expires_at),
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
it('should update content for existing feature items', async () => {
|
|
// Wipe items from initial sync
|
|
await application.itemManager.removeAllItemsFromMemory()
|
|
// Wipe roles from initial sync
|
|
await application.featuresService.setRoles([])
|
|
// Create pre-existing item for theme without all the info
|
|
await application.itemManager.createItem(
|
|
ContentType.Theme,
|
|
FillItemContent({
|
|
package_info: {
|
|
identifier: FeatureIdentifier.MidnightTheme,
|
|
},
|
|
}),
|
|
)
|
|
// Call sync intentionally to get roles again in meta
|
|
await application.sync.sync()
|
|
// Timeout since we don't await for features update
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
expect(application.itemManager.changeComponent.callCount).to.equal(1)
|
|
const themeItems = application.items.getItems(ContentType.Theme)
|
|
expect(themeItems).to.have.lengthOf(1)
|
|
expect(themeItems[0].content).to.containSubset(
|
|
JSON.parse(
|
|
JSON.stringify({
|
|
package_info: midnightThemeFeature,
|
|
valid_until: new Date(midnightThemeFeature.expires_at),
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
it('should delete theme item if feature has expired', async () => {
|
|
const now = new Date()
|
|
const yesterday = now.setDate(now.getDate() - 1)
|
|
|
|
getUserFeatures.restore()
|
|
sinon.stub(application.apiService, 'getUserFeatures').callsFake(() => {
|
|
return Promise.resolve({
|
|
data: {
|
|
features: [
|
|
{
|
|
...midnightThemeFeature,
|
|
expires_at: yesterday,
|
|
},
|
|
],
|
|
},
|
|
})
|
|
})
|
|
|
|
const themeItem = application.items.getItems(ContentType.Theme)[0]
|
|
|
|
// Wipe roles from initial sync
|
|
await application.featuresService.setRoles([])
|
|
|
|
// Call sync intentionally to get roles again in meta
|
|
await application.sync.sync()
|
|
|
|
// Timeout since we don't await for features update
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
expect(application.itemManager.setItemsToBeDeleted.calledWith([sinon.match({ uuid: themeItem.uuid })])).to.equal(
|
|
true,
|
|
)
|
|
|
|
const noTheme = application.items.getItems(ContentType.Theme)[0]
|
|
expect(noTheme).to.not.be.ok
|
|
})
|
|
})
|
|
|
|
it('should provide feature', async () => {
|
|
const feature = application.features.getUserFeature(FeatureIdentifier.PlusEditor)
|
|
expect(feature).to.containSubset(plusEditorFeature)
|
|
})
|
|
|
|
describe('extension repo items observer', () => {
|
|
it('should migrate to user setting when extension repo is added', async () => {
|
|
sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => {
|
|
return false
|
|
})
|
|
|
|
expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false)
|
|
|
|
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
|
|
|
const promise = new Promise((resolve) => {
|
|
sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve)
|
|
})
|
|
|
|
await application.itemManager.createItem(
|
|
ContentType.ExtensionRepo,
|
|
FillItemContent({
|
|
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
|
}),
|
|
)
|
|
|
|
await promise
|
|
})
|
|
|
|
it('signing into account with ext repo should migrate it', async () => {
|
|
sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => {
|
|
return false
|
|
})
|
|
/** Attach an ExtensionRepo object to an account, but prevent it from being migrated.
|
|
* Then sign out, sign back in, and ensure the item is migrated. */
|
|
/** Prevent migration from running */
|
|
sinon
|
|
.stub(application.featuresService, 'migrateFeatureRepoToUserSetting')
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
.callsFake(() => {})
|
|
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
|
await application.itemManager.createItem(
|
|
ContentType.ExtensionRepo,
|
|
FillItemContent({
|
|
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
|
}),
|
|
true,
|
|
)
|
|
await application.sync.sync()
|
|
application = await Factory.signOutApplicationAndReturnNew(application)
|
|
|
|
sinon.restore()
|
|
sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => {
|
|
return false
|
|
})
|
|
const promise = new Promise((resolve) => {
|
|
sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve)
|
|
})
|
|
await Factory.loginToApplication({
|
|
application,
|
|
email,
|
|
password,
|
|
})
|
|
await promise
|
|
})
|
|
|
|
it('having an ext repo with no account, then signing into account, should migrate it', async () => {
|
|
application = await Factory.signOutApplicationAndReturnNew(application)
|
|
sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => {
|
|
return false
|
|
})
|
|
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
|
await application.itemManager.createItem(
|
|
ContentType.ExtensionRepo,
|
|
FillItemContent({
|
|
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
|
}),
|
|
true,
|
|
)
|
|
await application.sync.sync()
|
|
|
|
const promise = new Promise((resolve) => {
|
|
sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve)
|
|
})
|
|
await Factory.loginToApplication({
|
|
application,
|
|
email,
|
|
password,
|
|
})
|
|
await promise
|
|
})
|
|
|
|
it.skip('migrated ext repo should have property indicating it was migrated', async () => {
|
|
sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => {
|
|
return false
|
|
})
|
|
expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false)
|
|
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
|
const promise = new Promise((resolve) => {
|
|
application.streamItems(ContentType.ExtensionRepo, ({ changed }) => {
|
|
for (const item of changed) {
|
|
if (item.content.migratedToUserSetting) {
|
|
resolve()
|
|
}
|
|
}
|
|
})
|
|
})
|
|
await application.itemManager.createItem(
|
|
ContentType.ExtensionRepo,
|
|
FillItemContent({
|
|
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
|
}),
|
|
)
|
|
await promise
|
|
})
|
|
})
|
|
|
|
describe('offline features migration', () => {
|
|
it('previous extension repo should be migrated to offline feature repo', async () => {
|
|
application = await Factory.signOutApplicationAndReturnNew(application)
|
|
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
|
await application.itemManager.createItem(
|
|
ContentType.ExtensionRepo,
|
|
FillItemContent({
|
|
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
|
}),
|
|
true,
|
|
)
|
|
await application.sync.sync()
|
|
|
|
const repo = application.featuresService.getOfflineRepo()
|
|
expect(repo.migratedToOfflineEntitlements).to.equal(true)
|
|
expect(repo.offlineFeaturesUrl).to.equal('https://api.standardnotes.com/v1/offline/features')
|
|
expect(repo.offlineKey).to.equal(extensionKey)
|
|
})
|
|
})
|
|
})
|