feat(audit): Batch 23 — settings key renames with migration (CC1) + config.ts (CC11)
CC1: customCommandEnabled→enableCustomCommands, downloadArchive→ useDownloadArchive, clipboardWatch→watchClipboard, sidebarCollapsed→ isSidebarCollapsed, hardwareAcceleration→useHardwareAcceleration, videoDir/audioDir→videoFolder/audioFolder (+ chooseDir/clearDir store actions → chooseFolder/clearFolder). Rename map in settingsMigration.ts (pure, unit-tested incl. pre-rename backup fixture), applied as an idempotent on-disk shim at store open and on backup import so old settings.json and old backups both keep working. CC11: update host/owner/repo + release API moved to src/main/config.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -389,7 +389,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
// The core fix: a renderer-supplied extraArgs must NOT run while the gate is off.
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: false,
|
||||
enableCustomCommands: false,
|
||||
perDownloadExtraArgs: '--exec "calc.exe"',
|
||||
defaultTemplateId: 'danger',
|
||||
templates
|
||||
@@ -400,7 +400,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
it('returns [] when disabled even if a default template id is set', () => {
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: false,
|
||||
enableCustomCommands: false,
|
||||
perDownloadExtraArgs: undefined,
|
||||
defaultTemplateId: 'thumb',
|
||||
templates
|
||||
@@ -411,7 +411,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
it('parses a per-download override when enabled', () => {
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: true,
|
||||
enableCustomCommands: true,
|
||||
perDownloadExtraArgs: '--write-thumbnail --no-mtime',
|
||||
defaultTemplateId: null,
|
||||
templates
|
||||
@@ -422,7 +422,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
it('an explicit empty override yields [] even with a default template set', () => {
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: true,
|
||||
enableCustomCommands: true,
|
||||
perDownloadExtraArgs: '',
|
||||
defaultTemplateId: 'thumb',
|
||||
templates
|
||||
@@ -433,7 +433,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
it('falls back to the default template when no per-download override is given', () => {
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: true,
|
||||
enableCustomCommands: true,
|
||||
perDownloadExtraArgs: undefined,
|
||||
defaultTemplateId: 'thumb',
|
||||
templates
|
||||
@@ -444,7 +444,7 @@ describe('selectExtraArgs — custom-command consent gate (audit F2)', () => {
|
||||
it('returns [] when the default template id matches nothing', () => {
|
||||
expect(
|
||||
selectExtraArgs({
|
||||
customCommandEnabled: true,
|
||||
enableCustomCommands: true,
|
||||
perDownloadExtraArgs: undefined,
|
||||
defaultTemplateId: 'missing',
|
||||
templates
|
||||
@@ -550,7 +550,7 @@ describe('format sorting (-S)', () => {
|
||||
|
||||
describe('selectExtraArgs url-pattern matching', () => {
|
||||
const base = {
|
||||
customCommandEnabled: true,
|
||||
enableCustomCommands: true,
|
||||
perDownloadExtraArgs: undefined,
|
||||
defaultTemplateId: null as string | null
|
||||
}
|
||||
|
||||
@@ -57,21 +57,21 @@ function bench(items: DownloadItem[], maxConcurrent: number, iterations: number)
|
||||
describe('pump() cost at scale (PERF3)', () => {
|
||||
it('a full scan+promote over 5,000 items is well under a millisecond-scale budget', () => {
|
||||
const median = bench(makeItems(5_000), 3 + 1_250, 50) // slots open → full pipeline
|
||||
|
||||
|
||||
console.log(`pump pipeline @5k items: median ${median.toFixed(3)} ms`)
|
||||
expect(median).toBeLessThan(5)
|
||||
})
|
||||
|
||||
it('a full scan+promote over 20,000 items stays inside a 5 ms budget', () => {
|
||||
const median = bench(makeItems(20_000), 3 + 5_000, 50)
|
||||
|
||||
|
||||
console.log(`pump pipeline @20k items: median ${median.toFixed(3)} ms`)
|
||||
expect(median).toBeLessThan(5)
|
||||
})
|
||||
|
||||
it('the no-free-slot fast path (the per-progress-event case) is trivially cheap', () => {
|
||||
const median = bench(makeItems(20_000), 1, 100) // running >= cap → early return
|
||||
|
||||
|
||||
console.log(`pump no-slot path @20k items: median ${median.toFixed(3)} ms`)
|
||||
expect(median).toBeLessThan(2)
|
||||
})
|
||||
|
||||
@@ -368,7 +368,7 @@ describe.skipIf(!RUN)('real yt-dlp downloads (buildArgs end-to-end)', () => {
|
||||
expect(base, 'restricted to [A-Za-z0-9._-]').toMatch(/^[A-Za-z0-9._-]+$/)
|
||||
}, 240_000)
|
||||
|
||||
it('downloadArchive: a second run of the same URL is skipped via the archive', () => {
|
||||
it('useDownloadArchive: a second run of the same URL is skipped via the archive', () => {
|
||||
const archive = join(outDir, 'archive.txt')
|
||||
const mk = (id: string): string[] =>
|
||||
buildArgs({
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { migrateSettingsKeys, RENAMED_SETTINGS_KEYS } from '../src/main/settingsMigration'
|
||||
import { DEFAULT_SETTINGS } from '@shared/ipc'
|
||||
|
||||
describe('migrateSettingsKeys (CC1)', () => {
|
||||
it('moves every legacy key to its new name', () => {
|
||||
const legacy = {
|
||||
customCommandEnabled: true,
|
||||
downloadArchive: true,
|
||||
clipboardWatch: false,
|
||||
sidebarCollapsed: true,
|
||||
hardwareAcceleration: true,
|
||||
videoDir: 'D:\\Media\\Video',
|
||||
audioDir: 'D:\\Media\\Audio'
|
||||
}
|
||||
const out = migrateSettingsKeys(legacy)
|
||||
expect(out).toEqual({
|
||||
enableCustomCommands: true,
|
||||
useDownloadArchive: true,
|
||||
watchClipboard: false,
|
||||
isSidebarCollapsed: true,
|
||||
useHardwareAcceleration: true,
|
||||
videoFolder: 'D:\\Media\\Video',
|
||||
audioFolder: 'D:\\Media\\Audio'
|
||||
})
|
||||
})
|
||||
|
||||
it('a value already present under the new name wins', () => {
|
||||
const out = migrateSettingsKeys({ videoDir: 'old', videoFolder: 'new' })
|
||||
expect(out).toEqual({ videoFolder: 'new' })
|
||||
})
|
||||
|
||||
it('passes non-renamed keys through untouched and does not mutate the input', () => {
|
||||
const input = { theme: 'dark', maxConcurrent: 3, videoDir: 'X' }
|
||||
const out = migrateSettingsKeys(input)
|
||||
expect(out.theme).toBe('dark')
|
||||
expect(out.maxConcurrent).toBe(3)
|
||||
expect(input.videoDir).toBe('X') // input untouched
|
||||
expect('videoDir' in out).toBe(false)
|
||||
})
|
||||
|
||||
it('restores a full pre-rename backup fixture into the current Settings shape', () => {
|
||||
// A settings object exactly as an older AeroFetch would have exported it:
|
||||
// current defaults, but with every renamed key under its OLD name.
|
||||
const legacyBackup: Record<string, unknown> = { ...DEFAULT_SETTINGS }
|
||||
for (const [oldKey, newKey] of RENAMED_SETTINGS_KEYS) {
|
||||
legacyBackup[oldKey] = legacyBackup[newKey]
|
||||
delete legacyBackup[newKey]
|
||||
}
|
||||
legacyBackup.customCommandEnabled = true
|
||||
legacyBackup.videoDir = 'E:\\Downloads'
|
||||
|
||||
const out = migrateSettingsKeys(legacyBackup)
|
||||
// Every current key present, no legacy key left behind.
|
||||
for (const [oldKey, newKey] of RENAMED_SETTINGS_KEYS) {
|
||||
expect(oldKey in out).toBe(false)
|
||||
expect(newKey in out).toBe(true)
|
||||
}
|
||||
expect(out.enableCustomCommands).toBe(true)
|
||||
expect(out.videoFolder).toBe('E:\\Downloads')
|
||||
expect(Object.keys(out).sort()).toEqual(Object.keys(DEFAULT_SETTINGS).sort())
|
||||
})
|
||||
|
||||
it('every new name follows the boolean/folder naming convention', () => {
|
||||
for (const [, newKey] of RENAMED_SETTINGS_KEYS) {
|
||||
expect(newKey).toMatch(/^(is|has|should|use|enable|watch)[A-Z]|Folder$/)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user