From 99ba854ee169bb019fcdab442c71cad3441ac1ed Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Sat, 13 Sep 2025 06:17:40 -0700 Subject: [PATCH] Chore: Cli: Fix CLI app integration tests (#13089) --- .eslintignore | 1 + .gitignore | 1 + ...tion-tests.js => cli-integration-tests.ts} | 112 ++++++++++++++---- packages/lib/BaseApplication.ts | 4 + 4 files changed, 92 insertions(+), 26 deletions(-) rename packages/app-cli/app/{cli-integration-tests.js => cli-integration-tests.ts} (66%) diff --git a/.eslintignore b/.eslintignore index 06320fd8a5..1f936b453e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -96,6 +96,7 @@ packages/onenote-converter/pkg/onenote_converter.js packages/app-cli/app/LinkSelector.js packages/app-cli/app/app.js packages/app-cli/app/base-command.js +packages/app-cli/app/cli-integration-tests.js packages/app-cli/app/command-apidoc.js packages/app-cli/app/command-attach.js packages/app-cli/app/command-batch.js diff --git a/.gitignore b/.gitignore index b6a4d6c24d..a11d329095 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ docs/**/*.mustache packages/app-cli/app/LinkSelector.js packages/app-cli/app/app.js packages/app-cli/app/base-command.js +packages/app-cli/app/cli-integration-tests.js packages/app-cli/app/command-apidoc.js packages/app-cli/app/command-attach.js packages/app-cli/app/command-batch.js diff --git a/packages/app-cli/app/cli-integration-tests.js b/packages/app-cli/app/cli-integration-tests.ts similarity index 66% rename from packages/app-cli/app/cli-integration-tests.js rename to packages/app-cli/app/cli-integration-tests.ts index fb2fe6da85..83786ea81e 100644 --- a/packages/app-cli/app/cli-integration-tests.js +++ b/packages/app-cli/app/cli-integration-tests.ts @@ -2,33 +2,44 @@ /* eslint-disable no-console */ -const fs = require('fs-extra'); -const Logger = require('@joplin/utils/Logger').default; -const { dirname } = require('@joplin/lib/path-utils'); +import * as fs from 'fs-extra'; +import Logger, { TargetType } from '@joplin/utils/Logger'; +import { dirname } from '@joplin/lib/path-utils'; const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js'); -const JoplinDatabase = require('@joplin/lib/JoplinDatabase').default; -const BaseModel = require('@joplin/lib/BaseModel').default; -const Folder = require('@joplin/lib/models/Folder').default; -const Note = require('@joplin/lib/models/Note').default; -const Setting = require('@joplin/lib/models/Setting').default; +import JoplinDatabase from '@joplin/lib/JoplinDatabase'; +import BaseModel from '@joplin/lib/BaseModel'; +import Folder from '@joplin/lib/models/Folder'; +import Note from '@joplin/lib/models/Note'; +import Setting from '@joplin/lib/models/Setting'; const { sprintf } = require('sprintf-js'); const exec = require('child_process').exec; +const nodeSqlite = require('sqlite3'); +const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils'); +const { default: shimInitCli } = require('./utils/shimInitCli'); const baseDir = `${dirname(__dirname)}/tests/cli-integration`; const joplinAppPath = `${__dirname}/main.js`; +shimInitCli({ nodeSqlite, appVersion: () => require('../package.json').version, keytar: null }); +require('@joplin/lib/testing/test-utils'); + const logger = new Logger(); -logger.addTarget('console'); +logger.addTarget(TargetType.Console); logger.setLevel(Logger.LEVEL_ERROR); const dbLogger = new Logger(); -dbLogger.addTarget('console'); +dbLogger.addTarget(TargetType.Console); dbLogger.setLevel(Logger.LEVEL_INFO); const db = new JoplinDatabase(new DatabaseDriverNode()); db.setLogger(dbLogger); -function createClient(id) { +interface Client { + id: number; + profileDir: string; +} + +function createClient(id: number): Client { return { id: id, profileDir: `${baseDir}/client${id}`, @@ -37,13 +48,13 @@ function createClient(id) { const client = createClient(1); -function execCommand(client, command) { +function execCommand(client: Client, command: string) { const exePath = `node ${joplinAppPath}`; const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`; logger.info(`${client.id}: ${command}`); - return new Promise((resolve, reject) => { - exec(cmd, (error, stdout, stderr) => { + return new Promise((resolve, reject) => { + exec(cmd, (error: string, stdout: string, stderr: string) => { if (error) { logger.error(stderr); reject(error); @@ -54,17 +65,17 @@ function execCommand(client, command) { }); } -function assertTrue(v) { +function assertTrue(v: unknown) { if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v)); process.stdout.write('.'); } -function assertFalse(v) { +function assertFalse(v: unknown) { if (v) throw new Error(sprintf('Expected "false", got "%s"."', v)); process.stdout.write('.'); } -function assertEquals(expected, real) { +function assertEquals(expected: unknown, real: unknown) { if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real)); process.stdout.write('.'); } @@ -73,7 +84,7 @@ async function clearDatabase() { await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']); } -const testUnits = {}; +const testUnits: Record Promise> = {}; testUnits.testFolders = async () => { await execCommand(client, 'mkbook nb1'); @@ -85,10 +96,16 @@ testUnits.testFolders = async () => { await execCommand(client, 'mkbook nb1'); folders = await Folder.all(); - assertEquals(1, folders.length); + assertEquals(2, folders.length); assertEquals('nb1', folders[0].title); + assertEquals('nb1', folders[1].title); - await execCommand(client, 'rm -r -f nb1'); + await execCommand(client, 'rmbook -p -f nb1'); + + folders = await Folder.all(); + assertEquals(1, folders.length); + + await execCommand(client, 'rmbook -p -f nb1'); folders = await Folder.all(); assertEquals(0, folders.length); @@ -102,7 +119,7 @@ testUnits.testNotes = async () => { assertEquals(1, notes.length); assertEquals('n1', notes[0].title); - await execCommand(client, 'rm -f n1'); + await execCommand(client, 'rmnote -p -f n1'); notes = await Note.all(); assertEquals(0, notes.length); @@ -112,12 +129,19 @@ testUnits.testNotes = async () => { notes = await Note.all(); assertEquals(2, notes.length); - await execCommand(client, 'rm -f \'blabla*\''); + // Should fail to delete a non-existent note + let failed = false; + try { + await execCommand(client, 'rmnote -f \'blabla*\''); + } catch (error) { + failed = true; + } + assertEquals(failed, true); notes = await Note.all(); assertEquals(2, notes.length); - await execCommand(client, 'rm -f \'n*\''); + await execCommand(client, 'rmnote -f -p \'n*\''); notes = await Note.all(); assertEquals(0, notes.length); @@ -140,10 +164,12 @@ testUnits.testCat = async () => { testUnits.testConfig = async () => { await execCommand(client, 'config editor vim'); + await Setting.reset(); await Setting.load(); assertEquals('vim', Setting.value('editor')); await execCommand(client, 'config editor subl'); + await Setting.reset(); await Setting.load(); assertEquals('subl', Setting.value('editor')); @@ -201,15 +227,47 @@ testUnits.testMv = async () => { await execCommand(client, 'mknote note2'); await execCommand(client, 'mknote note3'); await execCommand(client, 'mknote blabla'); - await execCommand(client, 'mv \'note*\' nb2'); notes1 = await Note.previews(f1.id); notes2 = await Note.previews(f2.id); + assertEquals(4, notes1.length); + assertEquals(1, notes2.length); + + await execCommand(client, 'mv \'note*\' nb2'); + + notes2 = await Note.previews(f2.id); + notes1 = await Note.previews(f1.id); + assertEquals(1, notes1.length); assertEquals(4, notes2.length); }; +testUnits.testUse = async () => { + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mkbook nb2'); + await execCommand(client, 'mknote n1'); + await execCommand(client, 'mknote n2'); + + const f1 = await Folder.loadByTitle('nb1'); + const f2 = await Folder.loadByTitle('nb2'); + let notes1 = await Note.previews(f1.id); + let notes2 = await Note.previews(f2.id); + + assertEquals(0, notes1.length); + assertEquals(2, notes2.length); + + await execCommand(client, 'use nb1'); + await execCommand(client, 'mknote note2'); + await execCommand(client, 'mknote note3'); + + notes1 = await Note.previews(f1.id); + notes2 = await Note.previews(f2.id); + + assertEquals(2, notes1.length); + assertEquals(2, notes2.length); +}; + async function main() { await fs.remove(baseDir); @@ -217,7 +275,9 @@ async function main() { await db.open({ name: `${client.profileDir}/database.sqlite` }); BaseModel.setDb(db); - await Setting.load(); + Setting.setConstant('rootProfileDir', client.profileDir); + Setting.setConstant('profileDir', client.profileDir); + await loadKeychainServiceAndSettings([]); let onlyThisTest = 'testMv'; onlyThisTest = ''; @@ -234,7 +294,7 @@ async function main() { } } -main(process.argv).catch(error => { +main().catch(error => { console.info(''); logger.error(error); }); diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 80807f111c..bde7bf5830 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -891,6 +891,10 @@ export default class BaseApplication { if (!currentFolder) currentFolder = await Folder.defaultFolder(); Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : ''); + if (currentFolder && !this.hasGui()) { + this.currentFolder_ = currentFolder; + } + await setupAutoDeletion(); await MigrationService.instance().run();