Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+81
-24
@@ -87,9 +87,9 @@ describe('network options', () => {
|
||||
|
||||
it('emits --downloader <path> --downloader-args when an aria2c path is given', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, aria2cPath: 'C:/fake/bin/aria2c.exe' })
|
||||
expect(hasSeq(argv, '--downloader', 'C:/fake/bin/aria2c.exe', '--downloader-args', ARIA2C_ARGS)).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
hasSeq(argv, '--downloader', 'C:/fake/bin/aria2c.exe', '--downloader-args', ARIA2C_ARGS)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('omits --downloader when aria2cPath is not set', () => {
|
||||
@@ -144,7 +144,10 @@ describe('restrictFilenames / downloadArchivePath', () => {
|
||||
})
|
||||
|
||||
it('emits --download-archive <path> when a path is given', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, downloadArchivePath: 'C:/userdata/archive.txt' })
|
||||
const argv = build(opts(), dlo(), {
|
||||
...NO_ACCESS,
|
||||
downloadArchivePath: 'C:/userdata/archive.txt'
|
||||
})
|
||||
expect(hasSeq(argv, '--download-archive', 'C:/userdata/archive.txt')).toBe(true)
|
||||
})
|
||||
|
||||
@@ -162,7 +165,9 @@ describe('audio: -x --audio-format <fmt>', () => {
|
||||
})
|
||||
|
||||
it('maps the quality label to an --audio-quality value', () => {
|
||||
expect(hasSeq(build(audio({ quality: 'Best (MP3)' }), dlo()), '--audio-quality', '0')).toBe(true)
|
||||
expect(hasSeq(build(audio({ quality: 'Best (MP3)' }), dlo()), '--audio-quality', '0')).toBe(
|
||||
true
|
||||
)
|
||||
expect(hasSeq(build(audio({ quality: '320 kbps' }), dlo()), '--audio-quality', '320K')).toBe(
|
||||
true
|
||||
)
|
||||
@@ -247,18 +252,18 @@ describe('sponsorBlock', () => {
|
||||
|
||||
describe('preferredVideoCodec → -S res,fps,vcodec:<token>', () => {
|
||||
it('maps av1 to the av01 codec token', () => {
|
||||
expect(hasSeq(build(opts(), dlo({ preferredVideoCodec: 'av1' })), '-S', 'res,fps,vcodec:av01')).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
hasSeq(build(opts(), dlo({ preferredVideoCodec: 'av1' })), '-S', 'res,fps,vcodec:av01')
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('passes h264 / vp9 through verbatim', () => {
|
||||
expect(hasSeq(build(opts(), dlo({ preferredVideoCodec: 'h264' })), '-S', 'res,fps,vcodec:h264')).toBe(
|
||||
true
|
||||
)
|
||||
expect(hasSeq(build(opts(), dlo({ preferredVideoCodec: 'vp9' })), '-S', 'res,fps,vcodec:vp9')).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
hasSeq(build(opts(), dlo({ preferredVideoCodec: 'h264' })), '-S', 'res,fps,vcodec:h264')
|
||||
).toBe(true)
|
||||
expect(
|
||||
hasSeq(build(opts(), dlo({ preferredVideoCodec: 'vp9' })), '-S', 'res,fps,vcodec:vp9')
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('omits -S entirely when codec preference is "any"', () => {
|
||||
@@ -468,9 +473,9 @@ describe('YouTube extractor-args', () => {
|
||||
youtubePlayerClient: 'web_safari',
|
||||
youtubePoToken: 'web.gvs+ABC'
|
||||
})
|
||||
expect(hasSeq(argv, '--extractor-args', 'youtube:player_client=web_safari;po_token=web.gvs+ABC')).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
hasSeq(argv, '--extractor-args', 'youtube:player_client=web_safari;po_token=web.gvs+ABC')
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('emits only the fields that are set', () => {
|
||||
@@ -485,7 +490,10 @@ describe('YouTube extractor-args', () => {
|
||||
|
||||
describe('format sorting (-S)', () => {
|
||||
it('emits a raw -S string when formatSort is set, overriding the codec tiebreaker', () => {
|
||||
const argv = build(opts(), dlo({ formatSort: 'res:1080,vcodec:av01', preferredVideoCodec: 'h264' }))
|
||||
const argv = build(
|
||||
opts(),
|
||||
dlo({ formatSort: 'res:1080,vcodec:av01', preferredVideoCodec: 'h264' })
|
||||
)
|
||||
expect(hasSeq(argv, '-S', 'res:1080,vcodec:av01')).toBe(true)
|
||||
expect(hasSeq(argv, '-S', 'res,fps,vcodec:h264')).toBe(false)
|
||||
})
|
||||
@@ -578,6 +586,15 @@ describe('parseTrimSections', () => {
|
||||
expect(parseTrimSections('')).toEqual([])
|
||||
expect(parseTrimSections(undefined)).toEqual([])
|
||||
})
|
||||
|
||||
it('rejects times with more than two colon groups (L146)', () => {
|
||||
// H:MM:SS is the deepest valid form; a fourth component is nonsense and used
|
||||
// to slip through to yt-dlp, failing there instead of being rejected here.
|
||||
expect(parseTrimSections('1:2:3:4-5:6:7:8')).toEqual([])
|
||||
expect(parseTrimSections('0:00:00:01-0:00:00:02')).toEqual([])
|
||||
// The deepest valid form still passes.
|
||||
expect(parseTrimSections('1:02:03-1:02:04')).toEqual(['*1:02:03-1:02:04'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('trim (--download-sections)', () => {
|
||||
@@ -655,12 +672,12 @@ describe('collectionOutputTemplate', () => {
|
||||
})
|
||||
|
||||
it('zero-pads the index to three digits and clamps bad values to 001', () => {
|
||||
expect(collectionOutputTemplate('C:/o', { ...ctx, index: 42 }, 'f').replace(/\\/g, '/')).toContain(
|
||||
'/042 - f'
|
||||
)
|
||||
expect(collectionOutputTemplate('C:/o', { ...ctx, index: 0 }, 'f').replace(/\\/g, '/')).toContain(
|
||||
'/001 - f'
|
||||
)
|
||||
expect(
|
||||
collectionOutputTemplate('C:/o', { ...ctx, index: 42 }, 'f').replace(/\\/g, '/')
|
||||
).toContain('/042 - f')
|
||||
expect(
|
||||
collectionOutputTemplate('C:/o', { ...ctx, index: 0 }, 'f').replace(/\\/g, '/')
|
||||
).toContain('/001 - f')
|
||||
expect(
|
||||
collectionOutputTemplate('C:/o', { ...ctx, index: NaN }, 'f').replace(/\\/g, '/')
|
||||
).toContain('/001 - f')
|
||||
@@ -675,3 +692,43 @@ describe('collectionOutputTemplate', () => {
|
||||
expect(t.replace(/\\/g, '/')).toBe('C:/out/A B/Untitled/001 - %(title)s.%(ext)s')
|
||||
})
|
||||
})
|
||||
|
||||
describe('metadata overrides (--replace-in-metadata)', () => {
|
||||
it('emits --embed-metadata and --replace-in-metadata for each set field', () => {
|
||||
const argv = build(opts(), dlo({ embedMetadata: false, metadataTitle: 'My Track' }))
|
||||
expect(argv).toContain('--embed-metadata')
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'title', '^.*$', 'My Track')).toBe(true)
|
||||
})
|
||||
|
||||
it('emits all three override fields when all are set', () => {
|
||||
const argv = build(opts(), dlo({ metadataTitle: 'T', metadataArtist: 'A', metadataAlbum: 'B' }))
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'title', '^.*$', 'T')).toBe(true)
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'artist', '^.*$', 'A')).toBe(true)
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'album', '^.*$', 'B')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes a value containing a colon as a single arg without splitting', () => {
|
||||
const argv = build(opts(), dlo({ metadataTitle: 'Foo: Bar' }))
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'title', '^.*$', 'Foo: Bar')).toBe(true)
|
||||
})
|
||||
|
||||
it('escapes backslashes in the replacement string', () => {
|
||||
const argv = build(opts(), dlo({ metadataTitle: 'A\\B' }))
|
||||
expect(hasSeq(argv, '--replace-in-metadata', 'title', '^.*$', 'A\\\\B')).toBe(true)
|
||||
})
|
||||
|
||||
it('skips blank or whitespace-only values', () => {
|
||||
const argv = build(opts(), dlo({ metadataTitle: '', metadataArtist: ' ' }))
|
||||
expect(argv).not.toContain('--replace-in-metadata')
|
||||
})
|
||||
|
||||
it('forces --embed-metadata even when embedMetadata is false and an override is set', () => {
|
||||
const argv = build(opts(), dlo({ embedMetadata: false, metadataAlbum: 'Soundtrack' }))
|
||||
expect(argv).toContain('--embed-metadata')
|
||||
})
|
||||
|
||||
it('emits no override flags when no fields are set', () => {
|
||||
const argv = build(opts(), dlo({ embedMetadata: true }))
|
||||
expect(argv).not.toContain('--replace-in-metadata')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user