fix(core): QRZ password usable without keychain persistence + parseXml regression tests (stacked on #4043)#4044
Conversation
There was a problem hiding this comment.
Thanks @jensenpat — this is a clean, well-reasoned fix, and the "prove the test fails against the pre-#4043 parser" step (Principle VIII/XI) is exactly the discipline that would have caught the original regression. I reviewed against current main.
Heads-up: #4043 has already merged
Commit 1e301dc (#4043) is on main now, which is why this PR shows mergeable_state: dirty. A rebase onto current main will drop the overlapping parseXml guard and reduce the diff to your actual contribution — no action needed beyond the rebase you already anticipated in the description.
What I verified (all good)
- Session-password fallback (
CallsignLookupService): the memory-onlym_sessionPasswordis applied both in the keychain-less#elsepath and in theHAVE_KEYCHAINread-failure branch, so it also rescues the locked-keychain / ACL-denied case on keychain-enabled builds — nice touch beyond the stated scope.savePassword()assignsm_sessionPasswordbefore the empty-check, so clearing the field correctly clears the session copy too. Never touches disk, consistent with the existingQrzClient::m_passwordhandling. - Regression tests:
parseXmlisstatic, so exposing it public-for-tests is the minimal surface change. Field names (call,firstName,city,grid,licenseClass,hasLatLon,latitude/longitude) matchCallsignInfo/the parser,<cmath>is already included forstd::abs, and the three fixtures pin the real<QRZDatabase>-wrapped shapes including the error/no-key timeout path. - CMake: adding
QrzClient.cpp+Qt6::Networktoqrz_callsign_testis required for theQNetworkReplylink; the Windows/macOS keychain runtime-lib copies are all guarded byWIN32/APPLE+EXISTS, so they're no-ops where the staged libs aren't present and don't perturb the packaged (windeployqt/macdeployqt) paths.
No blocking issues from me — the code is correct and in-scope; just rebase now that #4043 has landed. Nice work.
🤖 aethersdr-agent · cost: $4.7873 · model: claude-opus-4-8
…l regression tests — Principle VIII. Two gaps surfaced by live testing on the keychain-less test-Mac build: 1. Session-password fallback. In a build without QtKeychain (or when the keychain read fails — locked keychain, ACL denial on an unsigned test binary), a password typed in Radio Setup → QRZ was never handed to QrzClient at all: savePassword() only warned, so lookups fell back to prefix cards even seconds after the operator entered valid credentials. CallsignLookupService now keeps a memory-only m_sessionPassword and applies it whenever the keychain can't supply one — persistence and usability are separate concerns. Never written to disk; a restart still requires re-entry when no keychain exists. 2. Parser regression tests. The aethersdr#4043 root-element bug (readElementText on <QRZDatabase> swallowed the whole response; login broken end-to-end since aethersdr#3990) shipped because parseXml had zero test coverage. ParsedResponse/parseXml are now public-for-tests, and qrz_callsign_test pins the three live response shapes: login (Session/Key), lookup (Callsign + Session), and session-timeout (Error, no Key). Verified the tests catch the original bug: against the pre-aethersdr#4043 parser they fail 6/6; with the aethersdr#4043 guard, all pass. Stacked on aethersdr#4043 (requires its parser guard for the new tests to pass). 💻 Generated with Claude Code (claude-fable-5 7/1/26) with architecture by @jensenpat
…inciple XI. macOS twin of the Windows DLL staging fix in aethersdr#4043: a staged third_party/qtkeychain resolves via an absolute rpath into the build checkout, so a bundle copied to another Mac (SFTP test deploys) fails to load libqt6keychain and the app dies at launch. Copy the dylib into Contents/Frameworks post-build and add the standard @executable_path/../Frameworks rpath at link time (linker-added rpath keeps the ad-hoc signature valid; macdeployqt release DMGs are unaffected). Proven by hiding the staging dir and launching — the app resolves the bundled copy. 💻 Generated with Claude Code (claude-fable-5 7/1/26) with architecture by @jensenpat
0989245 to
2c2e761
Compare
ten9876
left a comment
There was a problem hiding this comment.
Approving — this closes exactly the two follow-ups I asked for on #4043, and closes them well.
The regression test is the one that mattered. QRZ login broke end-to-end and shipped precisely because no test drove parseXml against a root-wrapped response. Your new cases pin all three real shapes: a full <QRZDatabase version="1.34" xmlns=…>-wrapped login yields the sessionKey, a lookup with Callsign+Session siblings retains it, and an expired body surfaces sessionError == "Session Timeout". That's the difference between "fixed" and "fixed to stay fixed" (Principle VIII) — a future token-walk refactor now trips a test instead of silently muting login. I ran the suite offscreen: ALL PASS, and a full build links clean (761/761).
The keychain-optional password fix is correct. The memory-only m_sessionPassword (documented — and verified — never written to disk) means a typed password works immediately when the keychain is empty, locked, or absent, while a stored keychain value still wins when present. That's exactly the failure I flagged on #4043 (keychain-missing → empty password → every lookup fails), and the precedence is right. No new persistence or security surface — the password already flowed as a QString.
Exposing parseXml/ParsedResponse as public static is the thin, stateless test seam I offered as the option — a clean way to pin the parser without a friend declaration. The macOS dylib-bundling commit layers correctly after the existing Windows staging block and is verifiable via check-macos.
Housekeeping: this was stacked on #4043's individual commits, which no longer matched main's squash-merge — I rebased it onto current origin/main (dropping the two already-landed commits) to clear the conflict and force-pushed with a lease; your two commits are unchanged and still yours. One pre-existing -Wunused-result on QTemporaryFile::open() in the cty.dat test (not touched by this PR) is worth folding into a future warning sweep, not a gate here.
Approving and enabling auto-merge. 73 — reviewed with Claude Code on behalf of KK7GWY.
Stacked on #4043 — merge that first. This branch contains #4043's commits plus one of mine; after #4043 merges, the diff reduces to my commit alone.
What live testing on a keychain-less build surfaced
Deploying a test build whose worktree had never run
scripts/setup/setup-qtkeychain.sh(sofind_package(Qt6Keychain QUIET)silently compiled persistence out) exposed two gaps:1. Typed password unusable without a keychain
In a keychain-less build — or when the keychain read fails (locked keychain, ACL denial on an unsigned test binary) — a password typed in Radio Setup → QRZ was never handed to
QrzClientat all:savePassword()only logged a warning. Lookups fell back to country prefix cards seconds after the operator entered valid credentials, which reads as "lookup is broken".CallsignLookupServicenow keeps a memory-only session password and applies it whenever the keychain can't supply one. Persistence and usability are separate concerns: without a keychain the password still works for the whole session and simply has to be re-entered after a restart (the QRZ tab's Test Login already worked, since it uses the field values directly). Never written to disk.2. Zero test coverage let the #4043 bug ship
The
<QRZDatabase>root-element bug (login broken end-to-end since #3990) shipped becauseparseXmlhad no tests.ParsedResponse/parseXmlare now public-for-tests with a comment saying why, andqrz_callsign_testpins the three live response shapes:Session/Key)Callsign+Session)Error, noKey)Verified the tests catch the original bug (Principle VIII/XI): against the pre-#4043 parser they fail 6/6; with #4043's guard, all 53 checks pass.
Testing
qrz_callsign_test: ALL PASS with fix(core): QRZ login broken end-to-end since #3990 #4043; 6 failures without it (regression coverage proven both ways).WRN aether.qrz: QRZ password persistence unavailable: built without QtKeychain→ prefix-fallback-only lookups; this PR + a properly staged qtkeychain build fix both symptoms.💻 Generated with Claude Code (claude-fable-5 7/1/26) with architecture by @jensenpat