Add updater skip-version regression coverage and shared version utils (v4.1.13)

This commit is contained in:
xRangerDE 2026-02-21 01:24:25 +01:00
parent 9c7de22c3a
commit d1fcbfaadb
6 changed files with 128 additions and 41 deletions

View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.12", "version": "4.1.13",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.12", "version": "4.1.13",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.12", "version": "4.1.13",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",
@ -8,10 +8,11 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "npm run build && electron .", "start": "npm run build && electron .",
"test:e2e:update-logic": "node scripts/smoke-test-update-version-logic.js",
"test:e2e": "npm exec --yes --package=playwright -- node scripts/smoke-test.js", "test:e2e": "npm exec --yes --package=playwright -- node scripts/smoke-test.js",
"test:e2e:guide": "npm exec --yes --package=playwright -- node scripts/smoke-test-template-guide.js", "test:e2e:guide": "npm exec --yes --package=playwright -- node scripts/smoke-test-template-guide.js",
"test:e2e:full": "npm exec --yes --package=playwright -- node scripts/smoke-test-full.js", "test:e2e:full": "npm exec --yes --package=playwright -- node scripts/smoke-test-full.js",
"test:e2e:release": "npm run build && npm run test:e2e && npm run test:e2e:guide && npm run test:e2e:full", "test:e2e:release": "npm run build && npm run test:e2e:update-logic && npm run test:e2e && npm run test:e2e:guide && npm run test:e2e:full",
"test:e2e:stress": "npm run test:e2e:release && npm run test:e2e:release && npm run test:e2e:release", "test:e2e:stress": "npm run test:e2e:release && npm run test:e2e:release && npm run test:e2e:release",
"pack": "npm run build && electron-builder --dir", "pack": "npm run build && electron-builder --dir",
"dist": "npm run build && electron-builder", "dist": "npm run build && electron-builder",

View File

@ -0,0 +1,84 @@
const path = require('path');
const {
normalizeUpdateVersion,
compareUpdateVersions,
isNewerUpdateVersion
} = require(path.join(process.cwd(), 'dist', 'update-version-utils.js'));
function run() {
const failures = [];
const assert = (condition, message) => {
if (!condition) failures.push(message);
};
const comparisons = [
{ left: '4.1.18', right: '4.1.10', expected: 1 },
{ left: '4.1.10', right: '4.1.18', expected: -1 },
{ left: 'v4.1.12', right: '4.1.12', expected: 0 },
{ left: '4.1.12', right: '4.1.12.1', expected: -1 },
{ left: '4.2.0', right: '4.1.999', expected: 1 },
{ left: '4.1.12-beta', right: '4.1.12', expected: 0 }
];
const compareResults = comparisons.map((testCase) => {
const actual = compareUpdateVersions(testCase.left, testCase.right);
const pass = actual === testCase.expected;
assert(pass, `compare failed: ${testCase.left} vs ${testCase.right} expected ${testCase.expected}, got ${actual}`);
return { ...testCase, actual, pass };
});
const skipVersionScenarios = [
{
name: 'old downloaded, newer available',
downloaded: '4.1.11',
latestKnown: '4.1.18',
expectedNeedsNewer: true
},
{
name: 'already latest downloaded',
downloaded: '4.1.18',
latestKnown: '4.1.18',
expectedNeedsNewer: false
},
{
name: 'downgrade should not trigger',
downloaded: '4.1.18',
latestKnown: '4.1.11',
expectedNeedsNewer: false
}
];
const scenarioResults = skipVersionScenarios.map((scenario) => {
const needsNewer = isNewerUpdateVersion(scenario.latestKnown, scenario.downloaded);
const pass = needsNewer === scenario.expectedNeedsNewer;
assert(pass, `${scenario.name} expected ${scenario.expectedNeedsNewer}, got ${needsNewer}`);
return { ...scenario, needsNewer, pass };
});
const normalizationChecks = {
fromVPrefix: normalizeUpdateVersion('v4.1.12') === '4.1.12',
trimmed: normalizeUpdateVersion(' 4.1.12 ') === '4.1.12'
};
assert(normalizationChecks.fromVPrefix, 'normalize did not remove v prefix');
assert(normalizationChecks.trimmed, 'normalize did not trim whitespace');
const summary = {
checks: {
compareResults,
scenarioResults,
normalizationChecks
},
failures
};
console.log(JSON.stringify(summary, null, 2));
if (failures.length) {
process.exitCode = 1;
}
}
run();

View File

@ -457,7 +457,7 @@
<div class="settings-card"> <div class="settings-card">
<h3 id="updateTitle">Updates</h3> <h3 id="updateTitle">Updates</h3>
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.1.12</p> <p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.1.13</p>
<button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button> <button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
</div> </div>
@ -502,7 +502,7 @@
<div class="status-dot" id="statusDot"></div> <div class="status-dot" id="statusDot"></div>
<span id="statusText">Nicht verbunden</span> <span id="statusText">Nicht verbunden</span>
</div> </div>
<span id="versionText">v4.1.12</span> <span id="versionText">v4.1.13</span>
</div> </div>
</main> </main>
</div> </div>

View File

@ -4,11 +4,12 @@ import * as fs from 'fs';
import { spawn, ChildProcess, execSync, exec, spawnSync } from 'child_process'; import { spawn, ChildProcess, execSync, exec, spawnSync } from 'child_process';
import axios from 'axios'; import axios from 'axios';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import { compareUpdateVersions, isNewerUpdateVersion, normalizeUpdateVersion } from './update-version-utils';
// ========================================== // ==========================================
// CONFIG & CONSTANTS // CONFIG & CONSTANTS
// ========================================== // ==========================================
const APP_VERSION = '4.1.12'; const APP_VERSION = '4.1.13';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json'; const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths // Paths
@ -2944,45 +2945,12 @@ function createWindow(): void {
// ========================================== // ==========================================
// AUTO-UPDATER (electron-updater) // AUTO-UPDATER (electron-updater)
// ========================================== // ==========================================
function normalizeUpdateVersion(version: string | null | undefined): string {
return (version || '').trim().replace(/^v/i, '');
}
function compareUpdateVersions(left: string | null | undefined, right: string | null | undefined): number {
const a = normalizeUpdateVersion(left);
const b = normalizeUpdateVersion(right);
if (!a && !b) return 0;
if (!a) return -1;
if (!b) return 1;
const aParts = a.split('.').map((part) => {
const numeric = Number(part.replace(/[^0-9].*$/, ''));
return Number.isFinite(numeric) ? numeric : 0;
});
const bParts = b.split('.').map((part) => {
const numeric = Number(part.replace(/[^0-9].*$/, ''));
return Number.isFinite(numeric) ? numeric : 0;
});
const maxLength = Math.max(aParts.length, bParts.length);
for (let i = 0; i < maxLength; i += 1) {
const av = aParts[i] || 0;
const bv = bParts[i] || 0;
if (av > bv) return 1;
if (av < bv) return -1;
}
return 0;
}
function hasNewerKnownUpdateThanDownloaded(): boolean { function hasNewerKnownUpdateThanDownloaded(): boolean {
if (!latestKnownUpdateVersion || !downloadedUpdateVersion) { if (!latestKnownUpdateVersion || !downloadedUpdateVersion) {
return false; return false;
} }
return compareUpdateVersions(latestKnownUpdateVersion, downloadedUpdateVersion) > 0; return isNewerUpdateVersion(latestKnownUpdateVersion, downloadedUpdateVersion);
} }
async function requestUpdateCheck(source: UpdateCheckSource, force = false): Promise<{ started: boolean; reason?: string }> { async function requestUpdateCheck(source: UpdateCheckSource, force = false): Promise<{ started: boolean; reason?: string }> {

View File

@ -0,0 +1,34 @@
export function normalizeUpdateVersion(version: string | null | undefined): string {
return (version || '').trim().replace(/^v/i, '');
}
function parseVersionPart(part: string): number {
const numeric = Number(part.replace(/[^0-9].*$/, ''));
return Number.isFinite(numeric) ? numeric : 0;
}
export function compareUpdateVersions(left: string | null | undefined, right: string | null | undefined): number {
const a = normalizeUpdateVersion(left);
const b = normalizeUpdateVersion(right);
if (!a && !b) return 0;
if (!a) return -1;
if (!b) return 1;
const aParts = a.split('.').map(parseVersionPart);
const bParts = b.split('.').map(parseVersionPart);
const maxLength = Math.max(aParts.length, bParts.length);
for (let i = 0; i < maxLength; i += 1) {
const av = aParts[i] || 0;
const bv = bParts[i] || 0;
if (av > bv) return 1;
if (av < bv) return -1;
}
return 0;
}
export function isNewerUpdateVersion(candidate: string | null | undefined, baseline: string | null | undefined): boolean {
return compareUpdateVersions(candidate, baseline) > 0;
}