Files
infix/src/webui/internal/testutil/helpers.go
T
Joachim Wiberg a3766a87cb webui: add Configure mode with System, Users, and NACM pages
Implements the configure accordion section with a candidate datastore
workflow: opening Configure copies running → candidate (Enter), each
card saves directly to candidate, Apply copies candidate → running
atomically, and Abort discards by resetting candidate from running.

- RESTCONF client: add Put, Patch, Delete, GetDatastore, PutDatastore,
  CopyDatastore, and a shared doRequest/writeJSON helper
- Handlers: ConfigureHandler (enter/apply/abort), ConfigureSystemHandler
  (hostname, clock, NTP, DNS, motd, editor), ConfigureUsersHandler
  (add/delete user, shell, password, SSH keys, NACM group membership)
- Status > NACM page: read-only group permission matrix derived from
  ietf-netconf-acm groups + rule-list mappings
- Sticky Apply/Abort toolbar with custom confirm dialog; server returns
  HX-Redirect:/ so HTMX does a full-page reload, keeping sidebar in sync
- Accordion persistence fixed: Configure excluded from localStorage so it
  never auto-reopens after Abort/Apply; restore no longer closes an
  accordion that contains the active page
- Password hashing by shelling out to mkpasswd(1)

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-03 13:57:09 +02:00

110 lines
2.2 KiB
Go

// SPDX-License-Identifier: MIT
package testutil
import (
"context"
"encoding/json"
"sync"
)
type mockEntry struct {
body any
err error
}
type MockFetcher struct {
mu sync.Mutex
responses map[string]mockEntry
errors map[string]error
}
func NewMockFetcher() *MockFetcher {
return &MockFetcher{
responses: make(map[string]mockEntry),
errors: make(map[string]error),
}
}
func (m *MockFetcher) SetResponse(path string, body any) {
m.mu.Lock()
defer m.mu.Unlock()
m.responses[path] = mockEntry{body: body}
}
func (m *MockFetcher) SetError(path string, err error) {
m.mu.Lock()
defer m.mu.Unlock()
m.errors[path] = err
}
func (m *MockFetcher) Get(_ context.Context, path string, target any) error {
m.mu.Lock()
defer m.mu.Unlock()
if err, ok := m.errors[path]; ok {
return err
}
entry, ok := m.responses[path]
if !ok {
return nil
}
raw, err := json.Marshal(entry.body)
if err != nil {
return err
}
return json.Unmarshal(raw, target)
}
func (m *MockFetcher) GetRaw(_ context.Context, path string) ([]byte, error) {
m.mu.Lock()
defer m.mu.Unlock()
if err, ok := m.errors[path]; ok {
return nil, err
}
entry, ok := m.responses[path]
if !ok {
return []byte("{}"), nil
}
return json.Marshal(entry.body)
}
func (m *MockFetcher) Post(_ context.Context, path string) error {
m.mu.Lock()
defer m.mu.Unlock()
if err, ok := m.errors[path]; ok {
return err
}
return nil
}
func (m *MockFetcher) PostJSON(_ context.Context, path string, _ any) error {
m.mu.Lock()
defer m.mu.Unlock()
if err, ok := m.errors[path]; ok {
return err
}
return nil
}
func (m *MockFetcher) Put(_ context.Context, _ string, _ any) error { return nil }
func (m *MockFetcher) Patch(_ context.Context, _ string, _ any) error { return nil }
func (m *MockFetcher) Delete(_ context.Context, _ string) error { return nil }
func (m *MockFetcher) GetDatastore(_ context.Context, _ string) (json.RawMessage, error) {
return json.RawMessage("{}"), nil
}
func (m *MockFetcher) PutDatastore(_ context.Context, _ string, _ json.RawMessage) error {
return nil
}
func (m *MockFetcher) CopyDatastore(_ context.Context, _, _ string) error { return nil }