Files
Azornes-Comfyui-Model-Resolver/tests/test_model_hash_compare_methods.mjs

158 lines
7.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { modelHashCompareMethods } from '../web/resolver/views/model_hash_compare_methods.js';
import { normalizeSha256 } from '../web/resolver/utils/hash_utils.js';
const methods = modelHashCompareMethods;
function createDialog() {
return {
...methods,
getFilenameFromPath: value => String(value || '').split(/[\\/]/).pop() || '',
getVersionedModelName: (name, version) => version ? `${name} ${version}` : name,
truncateText: value => value,
getLocalMatchIdentity: () => 'local-identity',
};
}
test('hash comparison normalizes and formats SHA-256 values', () => {
const dialog = createDialog();
const hash = 'A'.repeat(64);
assert.equal(normalizeSha256(`sha256:${hash}`), hash.toLowerCase());
assert.equal(normalizeSha256('not-a-hash'), '');
assert.equal(dialog.formatSha256Short(hash), 'aaaaaaaa...aaaaaaaa');
});
test('hash candidates are collected recursively with source labels and deduplicated', () => {
const dialog = createDialog();
const first = 'a'.repeat(64);
const second = 'b'.repeat(64);
const candidates = dialog.collectHashCandidatesForCompare({
sha256: first,
hashes: { SHA256: first },
file_info: { hash: second },
files: [{ fileHash: second }],
});
assert.deepEqual(candidates.map(candidate => candidate.hash), [first, second]);
assert.equal(candidates[0].source, 'metadata');
assert.match(candidates[1].source, /file_info/);
assert.deepEqual(dialog.getLocalHashCandidatesForCompare({ sha256: first }), [{
hash: first,
source: 'local model metadata',
}]);
});
test('local hash compare metadata resolves path, filename, labels, and URLs', () => {
const dialog = createDialog();
const model = { path: 'models/checkpoints/example.safetensors' };
assert.equal(dialog.getLocalHashComparePath(model), model.path);
assert.equal(dialog.getHashCompareFilename(model), 'example.safetensors');
assert.equal(dialog.getHashCompareSourceLabel('civitai'), 'CivitAI');
assert.equal(dialog.getHashCompareSourceLabel('unknown_source'), 'unknown source');
assert.equal(dialog.getHashCompareResultName({ name: 'Model', version_name: 'v2' }), 'Model v2');
assert.equal(dialog.getHashCompareResultUrl({ version_url: 'https://example.test/version' }), 'https://example.test/version');
});
test('hash compare matches are deduplicated and local identity prefers explicit values', () => {
const dialog = createDialog();
const matches = [
{ sourceLabel: 'CivitAI', name: 'Model', url: 'https://example.test', sha256: 'a'.repeat(64) },
{ sourceLabel: 'CivitAI', name: 'Model', url: 'https://example.test', sha256: 'a'.repeat(64) },
{ sourceLabel: 'HuggingFace', name: 'Model', url: 'https://example.test', sha256: 'b'.repeat(64) },
];
assert.equal(dialog.dedupeHashCompareMatches(matches).length, 2);
assert.equal(dialog.getHashCompareLocalMatchIdentity({ local_match_identity: 'explicit' }), 'explicit');
assert.equal(dialog.getHashCompareLocalMatchIdentity({}), 'local-identity');
});
test('hash compare metadata update stores normalized SHA-256 fields', () => {
const dialog = createDialog();
const model = {};
dialog.updateHashCompareModelMetadata(model, {
sha256: `sha256:${'c'.repeat(64)}`,
metadata_path: 'model.json',
});
assert.equal(model.sha256, 'c'.repeat(64));
assert.equal(model.hash, 'c'.repeat(64));
assert.equal(model.hashes.SHA256, 'c'.repeat(64));
assert.equal(model.metadata_path, 'model.json');
});
test('hash candidate collection handles primitive, array, null, and deeply nested metadata', () => {
const dialog = createDialog();
const hash = 'd'.repeat(64);
const nested = { sha256: hash };
for (let depth = 0; depth < 7; depth += 1) {
Object.assign(nested, { metadata: { sha256: hash } });
}
assert.deepEqual(dialog.collectHashCandidatesForCompare(hash, 'direct'), [{ hash, source: 'direct' }]);
assert.deepEqual(dialog.collectHashCandidatesForCompare([hash, null, 12], 'array'), [{ hash, source: 'array' }]);
assert.deepEqual(dialog.collectHashCandidatesForCompare(null), []);
assert.deepEqual(dialog.collectHashCandidatesForCompare(true), []);
assert.equal(dialog.collectHashCandidatesForCompare(nested).length, 1);
});
test('hash compare metadata uses path and filename fallbacks', () => {
const dialog = createDialog();
assert.equal(dialog.getLocalHashComparePath({ open_path: 'open/model.safetensors', path: 'path/model.safetensors' }), 'open/model.safetensors');
assert.equal(dialog.getLocalHashComparePath({ resolved_path: 'resolved/model.safetensors' }), 'resolved/model.safetensors');
assert.equal(dialog.getLocalHashComparePath({ file_path: 'file/model.safetensors' }), 'file/model.safetensors');
assert.equal(dialog.getLocalHashComparePath({}), '');
assert.equal(dialog.getHashCompareFilename({ filename: 'explicit.safetensors', path: 'ignored.safetensors' }), 'explicit.safetensors');
assert.equal(dialog.getHashCompareFilename({ name: 'named-model' }), 'named-model');
assert.equal(dialog.getHashCompareFilename({}), 'Selected local model');
});
test('hash compare source labels cover explicit, known, and unknown providers', () => {
const dialog = createDialog();
assert.equal(dialog.getHashCompareSourceLabel('ignored', { sourceLabel: 'Custom source' }), 'Custom source');
assert.equal(dialog.getHashCompareSourceLabel('download-source'), 'Selected source');
assert.equal(dialog.getHashCompareSourceLabel('model-list'), 'Local Database');
assert.equal(dialog.getHashCompareSourceLabel('huggingface'), 'HuggingFace');
assert.equal(dialog.getHashCompareSourceLabel('lora-archive'), 'LoRA Archive');
assert.equal(dialog.getHashCompareSourceLabel('unknown-source'), 'unknown source');
assert.equal(dialog.getHashCompareSourceLabel('', {}), 'Source');
});
test('hash compare result names and URLs use ordered fallbacks and truncation', () => {
const dialog = createDialog();
dialog.truncateText = value => value.slice(0, 5);
assert.equal(dialog.getHashCompareResultName({ model_name: 'Model', version: 'v1' }), 'Model');
assert.equal(dialog.getHashCompareResultName({ repo_id: 'owner/repo' }), 'owner');
assert.equal(dialog.getHashCompareResultName({}), 'Model');
assert.equal(dialog.getHashCompareResultUrl({ model_url: 'https://model.test' }), 'https://model.test');
assert.equal(dialog.getHashCompareResultUrl({ download_url: 'https://download.test' }), 'https://download.test');
assert.equal(dialog.getHashCompareResultUrl({}), '');
});
test('hash compare metadata update ignores invalid values and normalizes hash containers', () => {
const dialog = createDialog();
const model = { hashes: ['invalid'] };
dialog.updateHashCompareModelMetadata(model, { sha256: 'invalid' });
assert.deepEqual(model, { hashes: ['invalid'] });
dialog.updateHashCompareModelMetadata(model, { hash: 'e'.repeat(64) });
assert.deepEqual(model.hashes, { SHA256: 'e'.repeat(64) });
assert.equal(model.metadata_path, undefined);
dialog.updateHashCompareModelMetadata(null, { sha256: 'f'.repeat(64) });
});
test('hash compare deduplication and local identity handle empty and missing values', () => {
const dialog = createDialog();
dialog.getLocalMatchIdentity = () => '';
assert.deepEqual(dialog.dedupeHashCompareMatches([null, undefined, {}]), [{}]);
assert.equal(dialog.getHashCompareLocalMatchIdentity({}), '');
});