159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
import test from 'ava'
|
|
import { promises as fs } from 'fs'
|
|
import path from 'path'
|
|
import proxyquire from 'proxyquire'
|
|
import { PackageManagerInterface } from '../app/javascripts/Main/Packages/PackageManagerInterface'
|
|
import { AppName } from '../app/javascripts/Main/strings'
|
|
import { ensureDirectoryExists, readJSONFile } from '../app/javascripts/Main/Utils/FileUtils'
|
|
import makeFakePaths from './fakePaths'
|
|
import { createTmpDir } from './testUtils'
|
|
|
|
const tmpDir = createTmpDir(__filename)
|
|
const FakePaths = makeFakePaths(tmpDir.path)
|
|
|
|
const contentDir = path.join(tmpDir.path, 'Extensions')
|
|
let downloadFileCallCount = 0
|
|
|
|
const { initializePackageManager } = proxyquire('../app/javascripts/Main/Packages/PackageManager', {
|
|
'./paths': {
|
|
Paths: FakePaths,
|
|
'@noCallThru': true,
|
|
},
|
|
'./networking': {
|
|
/** Download a fake component file */
|
|
async downloadFile(_src: string, dest: string) {
|
|
downloadFileCallCount += 1
|
|
if (!path.normalize(dest).startsWith(tmpDir.path)) {
|
|
throw new Error(`Bad download destination: ${dest}`)
|
|
}
|
|
await ensureDirectoryExists(path.dirname(dest))
|
|
await fs.copyFile(path.join(__dirname, 'data', 'zip-file.zip'), path.join(dest))
|
|
},
|
|
},
|
|
})
|
|
|
|
const fakeWebContents = {
|
|
send(_eventName: string, { error }) {
|
|
if (error) throw error
|
|
},
|
|
}
|
|
|
|
const name = 'Fake Component'
|
|
const identifier = 'fake.component'
|
|
const uuid = 'fake-component'
|
|
const version = '1.0.0'
|
|
const modifiers = Array(20)
|
|
.fill(0)
|
|
.map((_, i) => String(i).padStart(2, '0'))
|
|
|
|
function fakeComponent({ deleted = false, modifier = '' } = {}) {
|
|
return {
|
|
uuid: uuid + modifier,
|
|
deleted,
|
|
content: {
|
|
name: name + modifier,
|
|
autoupdateDisabled: false,
|
|
package_info: {
|
|
version,
|
|
identifier: identifier + modifier,
|
|
download_url: 'https://standardnotes.com',
|
|
url: 'https://standardnotes.com',
|
|
latest_url: 'https://standardnotes.com',
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
let packageManager: PackageManagerInterface
|
|
|
|
const log = console.log
|
|
const error = console.error
|
|
|
|
test.before(async function () {
|
|
/** Silence the package manager's output. */
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
console.log = () => {}
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
console.error = () => {}
|
|
await ensureDirectoryExists(contentDir)
|
|
packageManager = await initializePackageManager(fakeWebContents)
|
|
})
|
|
|
|
test.after.always(async function () {
|
|
console.log = log
|
|
console.error = error
|
|
await tmpDir.clean()
|
|
})
|
|
|
|
test.beforeEach(function () {
|
|
downloadFileCallCount = 0
|
|
})
|
|
|
|
test('installs multiple components', async (t) => {
|
|
await packageManager.syncComponents(modifiers.map((modifier) => fakeComponent({ modifier })))
|
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
|
|
const files = await fs.readdir(contentDir)
|
|
t.is(files.length, 1 + modifiers.length)
|
|
for (const modifier of modifiers) {
|
|
t.true(files.includes(identifier + modifier))
|
|
}
|
|
t.true(files.includes('mapping.json'))
|
|
const mappingContents = await fs.readFile(path.join(contentDir, 'mapping.json'), 'utf8')
|
|
|
|
t.deepEqual(
|
|
JSON.parse(mappingContents),
|
|
modifiers.reduce((acc, modifier) => {
|
|
acc[uuid + modifier] = {
|
|
location: path.join('Extensions', identifier + modifier),
|
|
version,
|
|
}
|
|
return acc
|
|
}, {}),
|
|
)
|
|
|
|
const downloads = await fs.readdir(path.join(tmpDir.path, AppName, 'downloads'))
|
|
t.is(downloads.length, modifiers.length)
|
|
for (const modifier of modifiers) {
|
|
t.true(downloads.includes(`${name + modifier}.zip`))
|
|
}
|
|
|
|
for (const modifier of modifiers) {
|
|
const componentFiles = await fs.readdir(path.join(contentDir, identifier + modifier))
|
|
t.is(componentFiles.length, 2)
|
|
}
|
|
})
|
|
|
|
test('uninstalls multiple components', async (t) => {
|
|
await packageManager.syncComponents(modifiers.map((modifier) => fakeComponent({ deleted: true, modifier })))
|
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
|
|
const files = await fs.readdir(contentDir)
|
|
t.deepEqual(files, ['mapping.json'])
|
|
|
|
t.deepEqual(await readJSONFile(path.join(contentDir, 'mapping.json')), {})
|
|
})
|
|
|
|
test("doesn't download anything when two install/uninstall tasks are queued", async (t) => {
|
|
await Promise.all([
|
|
packageManager.syncComponents([fakeComponent({ deleted: false })]),
|
|
packageManager.syncComponents([fakeComponent({ deleted: false })]),
|
|
packageManager.syncComponents([fakeComponent({ deleted: true })]),
|
|
])
|
|
t.is(downloadFileCallCount, 1)
|
|
})
|
|
|
|
test("Relies on download_url's version field to store the version number", async (t) => {
|
|
await packageManager.syncComponents([fakeComponent()])
|
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
|
|
const mappingFileVersion = JSON.parse(await fs.readFile(path.join(contentDir, 'mapping.json'), 'utf8'))[uuid].version
|
|
|
|
const packageJsonVersion = JSON.parse(
|
|
await fs.readFile(path.join(contentDir, identifier, 'package.json'), 'utf-8'),
|
|
).version
|
|
|
|
t.not(mappingFileVersion, packageJsonVersion)
|
|
t.is(mappingFileVersion, version)
|
|
})
|