From fda204602b5fa22f13d836cc4880f5482e6e9f54 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 2 Aug 2026 14:40:48 +0200 Subject: [PATCH] test(frontend): cover search hash policies --- tests/test_search_hash_methods.mjs | 114 +++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/test_search_hash_methods.mjs diff --git a/tests/test_search_hash_methods.mjs b/tests/test_search_hash_methods.mjs new file mode 100644 index 0000000..cdd8f2c --- /dev/null +++ b/tests/test_search_hash_methods.mjs @@ -0,0 +1,114 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { searchHashMethods } from '../web/resolver/search/search_hash_methods.js'; + +function createDialog(overrides = {}) { + return { ...searchHashMethods, ...overrides }; +} + +const hashA = 'a'.repeat(64); +const hashB = 'b'.repeat(64); + +test('search hash normalization accepts SHA-256 prefixes and rejects invalid values', () => { + const dialog = createDialog(); + assert.equal(dialog.normalizeSearchResultSha256(`sha256:${hashA.toUpperCase()}`), hashA); + assert.equal(dialog.normalizeSearchResultSha256(`SHA256=${hashB}`), hashB); + assert.equal(dialog.normalizeSearchResultSha256(hashA), hashA); + assert.equal(dialog.normalizeSearchResultSha256('short'), ''); + assert.equal(dialog.normalizeSearchResultSha256(`${hashA}x`), ''); + assert.equal(dialog.getSearchResultSha256(null), ''); + assert.equal(dialog.getSearchResultSha256({}), ''); +}); + +test('search hash extraction checks result and nested provider fields in priority order', () => { + const dialog = createDialog(); + assert.equal(dialog.getSearchResultSha256({ hash: 'invalid', file_info: { hash: hashB } }), hashB); + assert.equal(dialog.getSearchResultSha256({ hashes: { SHA256: hashA }, sha256: hashB }), hashB); + assert.equal(dialog.getSearchResultSha256({ file_info: { hashes: { sha256: hashA } } }), hashA); + assert.equal(dialog.getSearchResultSha256({ hashes: null, file_info: null }), ''); +}); + +test('hash label collection merges verified matches from missing data, state, and all providers', () => { + const dialog = createDialog({ + getLocalMatchHash: match => match.localHash || match.sha256 || '', + getSearchResultSha256: result => result.sha256 || '', + }); + const missing = { + matches: [ + { hash_match: true, localHash: hashA }, + { match_type: 'hash', sha256: hashA }, + { confidence: 100, sha256: hashB }, + ], + download_source: { hash_verified: true, sha256: hashB }, + }; + const results = { + local_hash_matches: [{ hash_lookup_source: 'local', sha256: hashA }], + popular: [{ hash_verified: true, sha256: hashB }], + model_list: [{ hash_verified_sha256: hashA }], + huggingface: [{ match_type: 'hash', sha256: hashB }], + civitai: [{ sha256: hashA }], + civarchive: [{ hash_verified: true, sha256: hashB }], + lora_manager_archive: [{ hash_verified: true, sha256: hashA }], + custom: [[{ hash_verified: true, sha256: hashB }]], + }; + + assert.deepEqual(dialog.collectHashLabelMapHashes(missing, results), [hashA, hashB]); + assert.deepEqual([...dialog.getHashMatchLabelMap(missing, results).entries()], [ + [hashA, 'Hash 1'], + [hashB, 'Hash 2'], + ]); +}); + +test('hash label collection can read search state and labels verified results', () => { + const state = { results: { popular: [{ hash_verified: true, sha256: hashA }] } }; + const dialog = createDialog({ + getSearchState: () => state, + getHashMatchLabelMap: searchHashMethods.getHashMatchLabelMap, + }); + const labelMap = dialog.getHashMatchLabelMap({}); + + assert.equal(labelMap.get(hashA), 'Hash'); + assert.equal(dialog.getHashMatchLabelForSearchResult({ sha256: hashA, hash_verified: true }, labelMap, []), 'Hash'); + assert.equal(dialog.getHashMatchLabelForSearchResult({ sha256: hashA }, labelMap, ['local-id']), 'Hash'); + assert.equal(dialog.getHashMatchLabelForSearchResult({ sha256: hashA, match_type: 'exact' }, labelMap, []), ''); + assert.equal(dialog.getHashMatchLabelForSearchResult({ sha256: hashB }, labelMap, []), ''); +}); + +test('hash source keys and local identities match by hash or normalized source', () => { + const dialog = createDialog({ + getLocalMatchIdentity: match => match.identity || '', + }); + const matches = [ + { hash_lookup_source: 'Civit-AI', sha256: hashA, identity: 'local-a' }, + { hash_lookup_source: 'huggingface', identity: 'local-hf' }, + { hash_lookup_source: 'civitai', sha256: hashB, identity: 'local-b' }, + null, + ]; + + assert.equal(dialog.normalizeHashLookupSourceKey(' Civit-AI '), 'civit_ai'); + assert.deepEqual( + dialog.getLocalHashMatchIdentitiesForResult(matches, 'civit-ai', { sha256: hashA }), + ['local-a'] + ); + assert.deepEqual( + dialog.getLocalHashMatchIdentitiesForResult(matches, 'hugging-face', {}), + [] + ); + assert.deepEqual( + dialog.getLocalHashMatchIdentitiesForResult( + [{ hash_lookup_source: 'hugging-face', identity: 'local-hf' }], + 'hugging-face', + { hash_verified_local_match_identities: ['saved-id'] } + ), + ['saved-id', 'local-hf'] + ); +}); + +test('local hash extraction checks direct, model, and nested hash fields', () => { + const dialog = createDialog(); + assert.equal(dialog.getLocalMatchHash({ sha256: hashA, model: { sha256: hashB } }), hashA); + assert.equal(dialog.getLocalMatchHash({ model: { hash: hashB } }), hashB); + assert.equal(dialog.getLocalMatchHash({ model: { hashes: { SHA256: hashA } } }), hashA); + assert.equal(dialog.getLocalMatchHash({}), ''); +});