Hostkeeper Test Plan — Phase 3
Created: 2025-01-31
Purpose: Comprehensive test coverage for all packages
Target Coverage: 90%+ for all packages
Current Coverage Summary
| Package |
Current |
Target |
Gap |
internal/errors |
100% |
100% |
— |
internal/models |
91% |
100% |
+9% |
pkg/crypto |
0% |
100% |
+100% |
pkg/knownhosts |
0% |
100% |
+100% |
pkg/ssh |
60% |
90% |
+30% |
pkg/storage |
40% |
90% |
+50% |
pkg/config |
22% |
80% |
+58% |
pkg/tui |
~40% |
70% |
+30% |
cmd/hostkeeper |
~40% |
70% |
+30% |
Test File Structure
Total: 7 new test files, ~87 test scenarios
Scenario Details
1. test/crypto/crypto_test.go — CRITICAL
| # |
Scenario |
Input |
Expected |
Priority |
| 1.1 |
DeriveKey determinism |
same password + salt |
same key |
HIGH |
| 1.2 |
DeriveKey password variation |
different password |
different key |
HIGH |
| 1.3 |
DeriveKey salt variation |
different salt |
different key |
HIGH |
| 1.4 |
DeriveKey empty password |
"" |
no panic, valid key |
MEDIUM |
| 1.5 |
Encrypt/Decrypt round-trip |
"hello world" |
decrypt = original |
HIGH |
| 1.6 |
Encrypt empty plaintext |
"" |
decrypt = "" |
MEDIUM |
| 1.7 |
Encrypt large data |
1MB random bytes |
round-trip OK |
MEDIUM |
| 1.8 |
Encrypt unicode |
"こんにちは" |
round-trip OK |
MEDIUM |
| 1.9 |
Encrypt with newlines |
"line1\nline2" |
round-trip OK |
MEDIUM |
| 1.10 |
Wrong password |
decrypt with wrong pw |
ErrDecryptionFailed |
HIGH |
| 1.11 |
Empty password |
decrypt with "" |
ErrDecryptionFailed |
HIGH |
| 1.12 |
IsEncrypted valid ciphertext |
base64 ciphertext |
true |
HIGH |
| 1.13 |
IsEncrypted plaintext |
"hello" |
false |
HIGH |
| 1.14 |
IsEncrypted empty |
"" |
false |
MEDIUM |
| 1.15 |
HashPassword determinism |
same pw |
same hash |
HIGH |
| 1.16 |
HashPassword variation |
different pw |
different hash |
HIGH |
| 1.17 |
Encrypt randomness |
same input, 2 calls |
different ciphertext |
MEDIUM |
2. test/knownhosts/knownhosts_test.go — CRITICAL
| # |
Scenario |
Input |
Expected |
Priority |
| 2.1 |
New creates file |
non-existent path |
file created |
HIGH |
| 2.2 |
New loads existing |
existing file |
hosts loaded |
HIGH |
| 2.3 |
Add new host |
hostname+port+key |
added |
HIGH |
| 2.4 |
Add duplicate |
same host twice |
no error, no duplicate |
HIGH |
| 2.5 |
Get existing |
hostname+port |
returns HostKey |
HIGH |
| 2.6 |
Get non-existent |
unknown host |
nil |
MEDIUM |
| 2.7 |
Remove existing |
hostname+port |
removed |
HIGH |
| 2.8 |
Remove non-existent |
unknown host |
no error |
MEDIUM |
| 2.9 |
Verify unknown |
new host |
(false, nil) TOFU |
HIGH |
| 2.10 |
Verify known match |
correct key |
(true, hostKey) |
HIGH |
| 2.11 |
Verify known mismatch |
wrong key |
(false, hostKey) MITM |
HIGH |
| 2.12 |
HostKeyCallback |
autoAdd=true |
adds unknown hosts |
HIGH |
| 2.13 |
Persistence |
Add → Save → New → Get |
found |
HIGH |
| 2.14 |
Corrupted file |
invalid JSON |
error |
MEDIUM |
3. test/storage/json_storage_test.go — HIGH
| # |
Scenario |
Input |
Expected |
Priority |
| 3.1 |
SaveKeyPair |
valid KeyPair |
success |
HIGH |
| 3.2 |
ListKeyPairs |
after save |
returns saved |
HIGH |
| 3.3 |
GetKeyPair found |
by ID |
returns KeyPair |
HIGH |
| 3.4 |
GetKeyPair not found |
unknown ID |
error |
MEDIUM |
| 3.5 |
DeleteKeyPair |
by ID |
removed |
HIGH |
| 3.6 |
DeleteKeyPair not found |
unknown ID |
no error |
MEDIUM |
| 3.7 |
SaveSnippet |
valid Snippet |
success |
HIGH |
| 3.8 |
ListSnippets |
after save |
returns saved |
HIGH |
| 3.9 |
GetSnippet found |
by ID |
returns Snippet |
HIGH |
| 3.10 |
GetSnippet not found |
unknown ID |
error |
MEDIUM |
| 3.11 |
DeleteSnippet |
by ID |
removed |
HIGH |
| 3.12 |
DeleteSnippet not found |
unknown ID |
no error |
MEDIUM |
| 3.13 |
SetPassword + SaveHost |
encrypted storage |
file encrypted |
HIGH |
| 3.14 |
IsDataEncrypted |
encrypted file |
true |
HIGH |
| 3.15 |
Wrong password load |
decrypt with wrong pw |
error |
HIGH |
| 3.16 |
MergeStrategyMerge |
import with merge |
keeps existing + adds new |
HIGH |
| 3.17 |
MergeStrategyReplace |
import with replace |
overwrites all |
HIGH |
| 3.18 |
SaveHost empty ID |
host with "" ID |
generates UUID |
MEDIUM |
4. test/config/config_test.go — MEDIUM
| # |
Scenario |
Input |
Expected |
Priority |
| 4.1 |
New first run |
no config file |
creates default |
HIGH |
| 4.2 |
New existing |
valid config file |
loads config |
HIGH |
| 4.3 |
Save |
modify + save |
persists |
HIGH |
| 4.4 |
UpdateAppConfig |
change theme |
saved |
HIGH |
| 4.5 |
GetConfigDir |
— |
valid path |
MEDIUM |
| 4.6 |
GetDataDir |
— |
valid path |
MEDIUM |
| 4.7 |
GetConfigFilePath |
— |
ends with config.json |
MEDIUM |
| 4.8 |
GetHostsFilePath |
— |
ends with hosts.json |
MEDIUM |
| 4.9 |
GetKeysFilePath |
— |
ends with keys.json |
MEDIUM |
| 4.10 |
GetSnippetsFilePath |
— |
ends with snippets.json |
MEDIUM |
5. test/tui/responsive_test.go — MEDIUM
| # |
Scenario |
Input |
Expected |
Priority |
| 5.1 |
WrapFooter short |
short string |
single line |
MEDIUM |
| 5.2 |
WrapFooter long |
long string |
multi-line |
MEDIUM |
| 5.3 |
WrapFooter empty |
"" |
"" |
LOW |
| 5.4 |
ClampWidth over |
width > max |
clamped to max |
MEDIUM |
| 5.5 |
ClampWidth under |
width < min |
clamped to min |
MEDIUM |
| 5.6 |
ClampWidth in range |
min < width < max |
unchanged |
MEDIUM |
| 5.7 |
TruncateStr short |
short string |
unchanged |
MEDIUM |
| 5.8 |
TruncateStr long |
long string |
truncated + … |
MEDIUM |
| 5.9 |
TruncateStr empty |
"" |
"" |
LOW |
| 5.10 |
TruncateStr unicode |
"こんにちは世界" |
correct width |
MEDIUM |
6. test/cmd/root_test.go — MEDIUM
| # |
Scenario |
Input |
Expected |
Priority |
| 6.1 |
RootCmd execute |
no args |
no error |
HIGH |
| 6.2 |
Version flag |
--version |
shows version |
MEDIUM |
| 6.3 |
FindHost by ID |
host ID |
returns host |
HIGH |
| 6.4 |
FindHost by name |
host name |
returns host |
HIGH |
| 6.5 |
FindHost by hostname |
IP/hostname |
returns host |
HIGH |
| 6.6 |
FindHost not found |
unknown |
error |
MEDIUM |
| 6.7 |
NewStorage no password |
--password="" |
unencrypted |
MEDIUM |
| 6.8 |
NewStorage with password |
--password="x" |
encrypted |
HIGH |
Execution Order
Success Criteria