Skip to content

Commit b464e85

Browse files
committed
test: add unit tests for ImportAuthorizedKeys and ExportSSHConfig functions
1 parent 927ba0d commit b464e85

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

internal/core/facades_more_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) 2026 Keymaster Team
2+
// Keymaster - SSH key management system
3+
// This source code is licensed under the MIT license found in the LICENSE file.
4+
5+
package core
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"errors"
11+
"strings"
12+
"testing"
13+
"time"
14+
15+
"github.com/toeirei/keymaster/internal/model"
16+
"github.com/toeirei/keymaster/internal/security"
17+
)
18+
19+
// fake KeyManager for ImportAuthorizedKeys
20+
type fmKeyManager struct {
21+
added []string
22+
failFor map[string]error
23+
}
24+
25+
func (f *fmKeyManager) AddPublicKey(algorithm, keyData, comment string, isGlobal bool, expiresAt time.Time) error {
26+
if f.failFor != nil {
27+
if e, ok := f.failFor[comment]; ok {
28+
return e
29+
}
30+
}
31+
f.added = append(f.added, comment)
32+
return nil
33+
}
34+
35+
func (f *fmKeyManager) GetGlobalPublicKeys() ([]model.PublicKey, error) { return nil, nil }
36+
func (f *fmKeyManager) GetKeysForAccount(accountID int) ([]model.PublicKey, error) { return nil, nil }
37+
func (f *fmKeyManager) AssignKeyToAccount(keyID, accountID int) error { return nil }
38+
39+
func TestImportAuthorizedKeys_Basic(t *testing.T) {
40+
data := "# header\nssh-ed25519 AAAA key-one\ninvalid-line\nssh-ed25519 BBBB key-two\nssh-ed25519 CCCC\n"
41+
km := &fmKeyManager{}
42+
r := strings.NewReader(data)
43+
imported, skipped, err := ImportAuthorizedKeys(context.TODO(), r, km, nil)
44+
if err != nil {
45+
t.Fatalf("unexpected error: %v", err)
46+
}
47+
if imported != 2 {
48+
t.Fatalf("expected 2 imported, got %d", imported)
49+
}
50+
if skipped < 1 {
51+
t.Fatalf("expected at least 1 skipped, got %d", skipped)
52+
}
53+
}
54+
55+
func TestExportSSHConfig_And_FindAccount(t *testing.T) {
56+
// empty
57+
stEmpty := &simpleStore{accounts: []model.Account{}}
58+
out, err := ExportSSHConfig(context.TODO(), stEmpty)
59+
if err != nil {
60+
t.Fatalf("ExportSSHConfig error: %v", err)
61+
}
62+
if out != "" {
63+
t.Fatalf("expected empty output for no accounts, got %q", out)
64+
}
65+
66+
// non-empty
67+
a1 := model.Account{ID: 1, Username: "alice", Hostname: "a.example.com", Label: ""}
68+
a2 := model.Account{ID: 2, Username: "bob", Hostname: "b.example.com", Label: "team"}
69+
st := &simpleStore{accounts: []model.Account{a1, a2}}
70+
out2, err := ExportSSHConfig(context.TODO(), st)
71+
if err != nil {
72+
t.Fatalf("ExportSSHConfig error: %v", err)
73+
}
74+
if !strings.Contains(out2, "HostName a.example.com") || !strings.Contains(out2, "User alice") {
75+
t.Fatalf("unexpected ssh config output: %q", out2)
76+
}
77+
78+
// FindAccountByIdentifier tests
79+
if acc, err := FindAccountByIdentifier("1", st.accounts); err != nil || acc == nil || acc.ID != 1 {
80+
t.Fatalf("FindAccountByIdentifier by id failed: %v %v", acc, err)
81+
}
82+
if acc, err := FindAccountByIdentifier("[email protected]", st.accounts); err != nil || acc == nil || acc.Username != "alice" {
83+
t.Fatalf("FindAccountByIdentifier by user@host failed: %v %v", acc, err)
84+
}
85+
if acc, err := FindAccountByIdentifier("team", st.accounts); err != nil || acc == nil || acc.Label != "team" {
86+
t.Fatalf("FindAccountByIdentifier by label failed: %v %v", acc, err)
87+
}
88+
if _, err := FindAccountByIdentifier("nope", st.accounts); err == nil {
89+
t.Fatalf("expected error for missing identifier")
90+
}
91+
}
92+
93+
func TestParallelRun_CollectsResults(t *testing.T) {
94+
a1 := model.Account{Username: "u1", Hostname: "h1"}
95+
a2 := model.Account{Username: "u2", Hostname: "h2"}
96+
accounts := []model.Account{a1, a2}
97+
worker := func(a model.Account) error {
98+
if a.Username == "u2" {
99+
return errors.New("boom")
100+
}
101+
return nil
102+
}
103+
res := ParallelRun(context.TODO(), accounts, worker)
104+
if len(res) != 2 {
105+
t.Fatalf("expected 2 results, got %d", len(res))
106+
}
107+
var errs int
108+
for _, r := range res {
109+
if r.Error != nil {
110+
errs++
111+
}
112+
}
113+
if errs != 1 {
114+
t.Fatalf("expected 1 error result, got %d", errs)
115+
}
116+
}
117+
118+
func TestWriteBackup_Compresses(t *testing.T) {
119+
data := &model.BackupData{SchemaVersion: 1}
120+
var buf bytes.Buffer
121+
if err := WriteBackup(context.TODO(), data, &buf); err != nil {
122+
t.Fatalf("WriteBackup failed: %v", err)
123+
}
124+
if buf.Len() == 0 {
125+
t.Fatalf("expected non-empty buffer after WriteBackup")
126+
}
127+
}
128+
129+
// DeployerManager that returns authorized_keys content
130+
type dmForImport struct{}
131+
132+
func (d *dmForImport) DeployForAccount(account model.Account, keepFile bool) error { return nil }
133+
func (d *dmForImport) AuditSerial(account model.Account) error { return nil }
134+
func (d *dmForImport) AuditStrict(account model.Account) error { return nil }
135+
func (d *dmForImport) DecommissionAccount(account model.Account, systemPrivateKey security.Secret, options interface{}) (DecommissionResult, error) {
136+
return DecommissionResult{}, nil
137+
}
138+
func (d *dmForImport) BulkDecommissionAccounts(accounts []model.Account, systemPrivateKey security.Secret, options interface{}) ([]DecommissionResult, error) {
139+
return nil, nil
140+
}
141+
func (d *dmForImport) CanonicalizeHostPort(host string) string { return host }
142+
func (d *dmForImport) ParseHostPort(host string) (string, string, error) { return host, "22", nil }
143+
func (d *dmForImport) GetRemoteHostKey(host string) (string, error) { return "hk", nil }
144+
func (d *dmForImport) FetchAuthorizedKeys(account model.Account) ([]byte, error) {
145+
return []byte("ssh-ed25519 AAAA key1\nssh-ed25519 BBBB key2\n"), nil
146+
}
147+
func (d *dmForImport) ImportRemoteKeys(account model.Account) ([]model.PublicKey, int, string, error) {
148+
return nil, 0, "", nil
149+
}
150+
func (d *dmForImport) IsPassphraseRequired(err error) bool { return false }
151+
152+
func TestRunImportRemoteCmd_Success(t *testing.T) {
153+
dm := &dmForImport{}
154+
km := &fmKeyManager{}
155+
imp, skip, warn, err := RunImportRemoteCmd(context.TODO(), model.Account{ID: 1}, dm, km, nil)
156+
if err != nil {
157+
t.Fatalf("RunImportRemoteCmd error: %v", err)
158+
}
159+
if imp != 2 || skip != 0 || warn != "" {
160+
t.Fatalf("unexpected import result: imp=%d skip=%d warn=%q", imp, skip, warn)
161+
}
162+
}

0 commit comments

Comments
 (0)