refactor: move V1 code into v1/ subdirectory
- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/ - Create v1/README.md with V1 documentation - Update root README for V1 + V2 structure - V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass - Root is now clean for V2 development
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
package knownhosts_test
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cryptossh "golang.org/x/crypto/ssh"
|
||||
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/pkg/knownhosts"
|
||||
)
|
||||
|
||||
func tempDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir, err := os.MkdirTemp("", "knownhosts-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { os.RemoveAll(dir) })
|
||||
return dir
|
||||
}
|
||||
|
||||
func generateTestKey(t *testing.T) cryptossh.PublicKey {
|
||||
t.Helper()
|
||||
pubKey, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
sshPubKey, err := cryptossh.NewPublicKey(pubKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SSH public key: %v", err)
|
||||
}
|
||||
return sshPubKey
|
||||
}
|
||||
|
||||
func generateTestKey2(t *testing.T) cryptossh.PublicKey {
|
||||
t.Helper()
|
||||
pubKey, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
sshPubKey, err := cryptossh.NewPublicKey(pubKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SSH public key: %v", err)
|
||||
}
|
||||
return sshPubKey
|
||||
}
|
||||
|
||||
// 2.1 New creates file on first call
|
||||
func TestNewCreatesFile(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
kh, err := knownhosts.New(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("New failed: %v", err)
|
||||
}
|
||||
if kh == nil {
|
||||
t.Fatal("New should return non-nil KnownHosts")
|
||||
}
|
||||
// File should be created after first Add+Save
|
||||
}
|
||||
|
||||
// 2.2 New loads existing
|
||||
func TestNewLoadsExisting(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
// Create and add a host
|
||||
kh1, _ := knownhosts.New(dir)
|
||||
_ = kh1.Add("example.com", 22, key)
|
||||
|
||||
// Load again
|
||||
kh2, err := knownhosts.New(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("New failed: %v", err)
|
||||
}
|
||||
stored := kh2.Get("example.com", 22)
|
||||
if stored == nil {
|
||||
t.Fatal("Should load existing host from file")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.3 Add new host
|
||||
func TestAddNewHost(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
err := kh.Add("example.com", 22, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Add failed: %v", err)
|
||||
}
|
||||
|
||||
stored := kh.Get("example.com", 22)
|
||||
if stored == nil {
|
||||
t.Fatal("Get should return the added host")
|
||||
}
|
||||
if stored.Hostname != "example.com" {
|
||||
t.Errorf("Hostname = %q, want %q", stored.Hostname, "example.com")
|
||||
}
|
||||
if stored.Port != 22 {
|
||||
t.Errorf("Port = %d, want 22", stored.Port)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.4 Add duplicate — no error, no duplicate
|
||||
func TestAddDuplicate(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
_ = kh.Add("example.com", 22, key)
|
||||
err := kh.Add("example.com", 22, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Add duplicate should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.5 Get existing host
|
||||
func TestGetExisting(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
_ = kh.Add("example.com", 22, key)
|
||||
|
||||
stored := kh.Get("example.com", 22)
|
||||
if stored == nil {
|
||||
t.Fatal("Get should return existing host")
|
||||
}
|
||||
if stored.Hostname != "example.com" {
|
||||
t.Errorf("Hostname = %q, want %q", stored.Hostname, "example.com")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.6 Get non-existent host
|
||||
func TestGetNonExistent(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
kh, _ := knownhosts.New(dir)
|
||||
|
||||
stored := kh.Get("unknown.com", 22)
|
||||
if stored != nil {
|
||||
t.Error("Get should return nil for non-existent host")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.7 Remove existing host
|
||||
func TestRemoveExisting(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
_ = kh.Add("example.com", 22, key)
|
||||
|
||||
err := kh.Remove("example.com", 22)
|
||||
if err != nil {
|
||||
t.Fatalf("Remove failed: %v", err)
|
||||
}
|
||||
|
||||
stored := kh.Get("example.com", 22)
|
||||
if stored != nil {
|
||||
t.Error("Get should return nil after Remove")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.8 Remove non-existent host — no error
|
||||
func TestRemoveNonExistent(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
kh, _ := knownhosts.New(dir)
|
||||
|
||||
err := kh.Remove("unknown.com", 22)
|
||||
if err != nil {
|
||||
t.Fatalf("Remove non-existent should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.9 Verify unknown host — (false, nil) TOFU
|
||||
func TestVerifyUnknown(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
matches, stored := kh.Verify("unknown.com", 22, key)
|
||||
if matches {
|
||||
t.Error("Verify should return false for unknown host")
|
||||
}
|
||||
if stored != nil {
|
||||
t.Error("Verify should return nil HostKey for unknown host")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.10 Verify known host, matching key — (true, hostKey)
|
||||
func TestVerifyKnownMatch(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
_ = kh.Add("example.com", 22, key)
|
||||
|
||||
matches, stored := kh.Verify("example.com", 22, key)
|
||||
if !matches {
|
||||
t.Error("Verify should return true for matching key")
|
||||
}
|
||||
if stored == nil {
|
||||
t.Error("Verify should return stored HostKey")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.11 Verify known host, mismatched key — (false, hostKey) MITM
|
||||
func TestVerifyKnownMismatch(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key1 := generateTestKey(t)
|
||||
key2 := generateTestKey2(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
_ = kh.Add("example.com", 22, key1)
|
||||
|
||||
matches, stored := kh.Verify("example.com", 22, key2)
|
||||
if matches {
|
||||
t.Error("Verify should return false for mismatched key (MITM)")
|
||||
}
|
||||
if stored == nil {
|
||||
t.Error("Verify should return stored HostKey for mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.12 HostKeyCallback — autoAdd=true adds unknown hosts
|
||||
func TestHostKeyCallbackAutoAdd(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh, _ := knownhosts.New(dir)
|
||||
callback := kh.HostKeyCallback(true)
|
||||
|
||||
// Simulate host key check via callback
|
||||
// HostKeyCallback expects net.Addr, so we create a fake one
|
||||
addr := &fakeAddr{addr: "192.168.1.1:22"}
|
||||
err := callback("example.com", addr, key)
|
||||
if err != nil {
|
||||
t.Fatalf("HostKeyCallback with autoAdd failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify host was added
|
||||
stored := kh.Get("example.com", 22)
|
||||
if stored == nil {
|
||||
t.Error("HostKeyCallback should add unknown host when autoAdd=true")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.13 Persistence — Add → Save → New → Get
|
||||
func TestPersistence(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
key := generateTestKey(t)
|
||||
|
||||
kh1, _ := knownhosts.New(dir)
|
||||
_ = kh1.Add("example.com", 22, key)
|
||||
|
||||
// Create new instance from same directory
|
||||
kh2, err := knownhosts.New(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("New failed: %v", err)
|
||||
}
|
||||
stored := kh2.Get("example.com", 22)
|
||||
if stored == nil {
|
||||
t.Fatal("Host should persist across New() calls")
|
||||
}
|
||||
}
|
||||
|
||||
// 2.14 Corrupted file → error
|
||||
func TestCorruptedFile(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
path := filepath.Join(dir, "known_hosts")
|
||||
_ = os.WriteFile(path, []byte("not valid json {{{"), 0600)
|
||||
|
||||
_, err := knownhosts.New(dir)
|
||||
if err == nil {
|
||||
t.Error("New should return error for corrupted file")
|
||||
}
|
||||
}
|
||||
|
||||
// fakeAddr implements net.Addr for testing
|
||||
type fakeAddr struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
func (f *fakeAddr) Network() string { return "tcp" }
|
||||
func (f *fakeAddr) String() string { return f.addr }
|
||||
Reference in New Issue
Block a user