Renames the product across the codebase: Go module path, config/data paths, keychain service name, export file extension, build tooling, docs, and UI text. Sidebar brand mark now reads ">_ AlchementaryLink" with "Alchementary" in the connection-group gold accent colour, and the window title is "Alchementary Link". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019T8bJS8BCtY5GeSG2HBj1n
143 lines
3.9 KiB
Go
143 lines
3.9 KiB
Go
package knownhosts
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/andydixon/link/internal/models"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mock storage implementation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type mockStorage struct {
|
|
hosts map[string]*models.KnownHost // keyed by "host:port"
|
|
}
|
|
|
|
func newMockStorage() *mockStorage {
|
|
return &mockStorage{hosts: make(map[string]*models.KnownHost)}
|
|
}
|
|
|
|
func (m *mockStorage) key(host string, port int) string {
|
|
return host + ":" + string(rune(port))
|
|
}
|
|
|
|
func (m *mockStorage) GetKnownHost(host string, port int) (*models.KnownHost, error) {
|
|
kh, ok := m.hosts[m.key(host, port)]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return kh, nil
|
|
}
|
|
|
|
func (m *mockStorage) SaveKnownHost(kh *models.KnownHost) error {
|
|
m.hosts[m.key(kh.Host, kh.Port)] = kh
|
|
return nil
|
|
}
|
|
|
|
func (m *mockStorage) ListKnownHosts() ([]*models.KnownHost, error) {
|
|
result := make([]*models.KnownHost, 0, len(m.hosts))
|
|
for _, kh := range m.hosts {
|
|
result = append(result, kh)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockStorage) DeleteKnownHost(id string) error {
|
|
for k, kh := range m.hosts {
|
|
if kh.ID == id {
|
|
delete(m.hosts, k)
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Verify unknown host returns false (no error)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestVerifyUnknownHost(t *testing.T) {
|
|
store := NewStore(newMockStorage())
|
|
|
|
trusted, err := store.Verify("unknown.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey")
|
|
if err != nil {
|
|
t.Fatalf("Verify() unexpected error = %v", err)
|
|
}
|
|
if trusted {
|
|
t.Error("expected trusted = false for unknown host")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Accept then verify returns true
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestAcceptThenVerify(t *testing.T) {
|
|
store := NewStore(newMockStorage())
|
|
|
|
if err := store.Accept("server.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey"); err != nil {
|
|
t.Fatalf("Accept() error = %v", err)
|
|
}
|
|
|
|
trusted, err := store.Verify("server.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey")
|
|
if err != nil {
|
|
t.Fatalf("Verify() error = %v", err)
|
|
}
|
|
if !trusted {
|
|
t.Error("expected trusted = true after Accept()")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Changed fingerprint returns error
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestVerifyChangedFingerprint(t *testing.T) {
|
|
store := NewStore(newMockStorage())
|
|
|
|
// Accept with fingerprint A.
|
|
if err := store.Accept("host.local", 22, "ssh-rsa", "SHA256:original", "pubkeyA"); err != nil {
|
|
t.Fatalf("Accept() error = %v", err)
|
|
}
|
|
|
|
// Verify with fingerprint B — should return an error indicating a change.
|
|
trusted, err := store.Verify("host.local", 22, "ssh-rsa", "SHA256:changed", "pubkeyB")
|
|
if err == nil {
|
|
t.Fatal("expected error for changed fingerprint")
|
|
}
|
|
if trusted {
|
|
t.Error("expected trusted = false for changed fingerprint")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// List and Delete operations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestListAndDelete(t *testing.T) {
|
|
mock := newMockStorage()
|
|
store := NewStore(mock)
|
|
|
|
_ = store.Accept("host-a.local", 22, "ssh-ed25519", "fp-a", "pk-a")
|
|
_ = store.Accept("host-b.local", 22, "ssh-rsa", "fp-b", "pk-b")
|
|
|
|
entries, err := store.List()
|
|
if err != nil {
|
|
t.Fatalf("List() error = %v", err)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Fatalf("expected 2 entries, got %d", len(entries))
|
|
}
|
|
|
|
// Delete the first entry.
|
|
if err := store.Delete(entries[0].ID); err != nil {
|
|
t.Fatalf("Delete() error = %v", err)
|
|
}
|
|
|
|
entries, _ = store.List()
|
|
if len(entries) != 1 {
|
|
t.Errorf("expected 1 entry after delete, got %d", len(entries))
|
|
}
|
|
}
|