Refactor test cases for improved readability and consistency

- Updated test_dns.py to enhance mock function definitions and improve spacing for better readability.
- Modified test_filters.py to streamline assertions and ensure consistent formatting across test cases.
- Cleaned up test_import_export.py by organizing imports and ensuring consistent formatting in CSV and JSON tests.
- Improved test_main.py by refining mock setups and ensuring consistent error handling in assertions.
This commit is contained in:
Philip Henning 2026-09-03 12:47:11 +02:00
parent 0710d10fac
commit 7872991e0b
42 changed files with 2376 additions and 2735 deletions

View file

@ -0,0 +1,7 @@
# Privileged mode gates system-file writes
The application starts read-only and permits changes to `/etc/hosts` only after
sudo access has been validated and a timestamped backup has been created. All
privileged writes go through `HostsManager` during that mode. This makes the
authorization boundary visible to the user and guarantees a pre-edit safety
snapshot; the temporary backup is not a user-facing recovery system.

View file

@ -0,0 +1,7 @@
# Normalize hosts-file serialization
The parser models Host Entries plus header and footer comments rather than the
file's exact byte layout. Serialization deliberately aligns fields, groups
comments, omits original blank-line placement, and ensures the management header
is present. This trades lossless round trips for a stable, readable output; code
and documentation must not promise preservation of original formatting.

36
docs/agents/domain.md Normal file
View file

@ -0,0 +1,36 @@
# Domain Docs
How engineering skills should consume this repository's domain documentation
when exploring the codebase.
## Before exploring, read these
- **`CONTEXT.md`** at the repository root.
- **`docs/adr/`** — read ADRs that affect the area about to be changed.
If either location does not exist, proceed silently. The `/domain-modeling`
skill creates domain documentation when terminology or decisions are resolved.
## File structure
This repository uses a single-context layout:
/
├── CONTEXT.md
├── docs/
│ └── adr/
└── src/
## Use the glossary's vocabulary
When output names a domain concept—for example in an issue title, refactor
proposal, hypothesis, or test name—use the term defined in `CONTEXT.md`. Avoid
synonyms that the glossary explicitly rejects.
If a required concept is missing, reconsider whether the proposed language
belongs to the project or note the gap for `/domain-modeling`.
## Flag ADR conflicts
Explicitly surface output that contradicts an existing ADR instead of silently
overriding it.

View file

@ -0,0 +1,52 @@
# Issue tracker: Forgejo
Issues and PRDs for this repository live as Forgejo issues in
`git.s1q.dev/phg/hosts`. Use `fgj` for all operations.
Every repository-scoped command must use the hostname `git.s1q.dev` and
repository `phg/hosts`.
## Conventions
- **Create an issue**:
`fgj --hostname git.s1q.dev issue create -R phg/hosts --title "..." --body "..."`
- **Read an issue**:
`fgj --hostname git.s1q.dev issue view <number> -R phg/hosts`
- **Read an issue as JSON**:
`fgj --hostname git.s1q.dev issue view <number> -R phg/hosts --json`
- **List issues**:
`fgj --hostname git.s1q.dev issue list -R phg/hosts --state open --json`
- **Comment on an issue**:
`fgj --hostname git.s1q.dev issue comment <number> -R phg/hosts --body "..."`
- **Apply or remove labels**:
`fgj --hostname git.s1q.dev issue edit <number> -R phg/hosts --add-label "..."`
or `--remove-label "..."`
- **Close an issue**:
`fgj --hostname git.s1q.dev issue close <number> -R phg/hosts --comment "..."`
- **List repository labels**:
`fgj --hostname git.s1q.dev label list -R phg/hosts --json`
## When a skill says "publish to the issue tracker"
Create a Forgejo issue with `fgj issue create`.
## When a skill says "fetch the relevant ticket"
Run `fgj issue view <number>` with the configured hostname and repository.
## Wayfinding operations
The map is a Forgejo issue with one child issue per ticket.
- **Map**: an issue labelled `wayfinder:map` containing Notes,
Decisions-so-far, and Fog sections.
- **Child ticket**: an issue whose body starts with `Part of #<map>` and whose
type is recorded with a `wayfinder:<type>` label (`research`, `prototype`,
`grilling`, or `task`).
- **Blocking**: put `Blocked by: #<number>, #<number>` at the top of the child
issue body. A ticket is unblocked when every listed issue is closed.
- **Frontier**: inspect the map's open child issues and choose the first issue
that has no open blocker and has not been claimed.
- **Claim**: apply the `wayfinder:claimed` label before beginning work.
- **Resolve**: comment with the answer, close the child issue, then append a
context pointer to the map's Decisions-so-far section.

View file

@ -0,0 +1,15 @@
# Triage Labels
The skills speak in terms of five canonical triage roles. This file maps those
roles to the label strings used in this repository's Forgejo issue tracker.
| Label in mattpocock/skills | Label in our tracker | Meaning |
| -------------------------- | -------------------- | ----------------------------------------- |
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
| `needs-info` | `needs-info` | Waiting on reporter for more information |
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
| `ready-for-human` | `ready-for-human` | Requires human implementation |
| `wontfix` | `wontfix` | Will not be actioned |
When a skill mentions a canonical role, use the corresponding tracker label
from this table.

253
docs/user-guide.md Normal file
View file

@ -0,0 +1,253 @@
# User guide
`hosts` is a keyboard-first terminal application for inspecting and editing the
system Hosts File. It starts in Read-only Mode and requires explicit sudo
authorization before it can change `/etc/hosts`.
> [!WARNING]
> `hosts` is alpha software. Read [Persistence and recovery](#persistence-and-recovery)
> before entering Privileged Mode. In particular, undo, redo, sorting, and save
> failures can currently leave the interface and `/etc/hosts` out of sync.
## Before you begin
You need macOS or Linux, Python 3.13 or newer, `sudo`, and
[uv](https://docs.astral.sh/uv/). The normal user entry point runs the current
revision from the project's default branch:
```bash
uvx git+https://git.s1q.dev/phg/hosts.git
```
The project does not yet publish versioned releases. The `uvx` command can
therefore run newer code on a later invocation.
## Inspect the Hosts File safely
The application opens in Read-only Mode. In this mode you can:
- select a Host Entry and inspect all of its hostnames, comment, state, and DNS
details;
- search by hostname, IP address, or comment;
- sort by IP address or Canonical Hostname;
- show or hide Default Entries from the configuration screen; and
- reload `/etc/hosts` from disk.
The footer shows `Read-only` while system-file mutations are blocked. Press `?`
for in-application help.
### A warning about sorting
Sorting is intended as a view operation, but currently reorders the in-memory
Hosts File. If you later enter Privileged Mode and save or perform an auto-saved
mutation, that sorted order can be written to `/etc/hosts`. Reload with `Ctrl+R`
before entering Privileged Mode if you sorted only for inspection.
## Enter Privileged Mode
Press `Ctrl+E`. The application first checks for existing sudo authorization.
If authorization is not already available, it asks for your password. Before
enabling mutations it verifies write access and creates a Pre-edit Backup.
If authorization, permission validation, or backup creation fails, the
application remains in Read-only Mode.
The footer shows `Edit` while Privileged Mode is active. This label refers to
Privileged Mode; it is separate from the Entry Editor used to change one Host
Entry.
## Change a Host Entry
In Privileged Mode:
- Press `n` to add a Host Entry.
- Select a non-default Host Entry and press `e` to open the Entry Editor.
- Press `d` and confirm to delete the selected Host Entry.
- Press `Space` to activate or deactivate the selected Host Entry.
- Press `Shift+Up` or `Shift+Down` to change its file order.
A Host Entry requires a valid IPv4 or IPv6 address, at least one valid hostname,
and may include a comment. The first hostname is its Canonical Hostname; any
additional hostnames are Aliases.
Default Entries represent protected baseline localhost or broadcasthost
mappings. They cannot be changed, deleted, activated, deactivated, or moved.
Most successful mutations are saved to `/etc/hosts` immediately. Read the
status message after every action. A message that reports a save failure means
the interface may have changed while the system file did not.
## Use a DNS Entry
Choose the DNS Name Entry type when adding or editing an entry, then provide:
- the DNS name whose address should be resolved; and
- one or more local hostnames that should map to that address.
The application resolves the DNS name in the background. A successful result
replaces the entry's stored IP address, records DNS metadata in the serialized
comment, activates a newly created DNS Entry, and saves the Hosts File. DNS
refresh is manual rather than continuous:
- Press `r` to refresh the selected DNS Entry.
- Press `Shift+R` to refresh all DNS Entries.
Both actions require Privileged Mode because a successful result changes
`/etc/hosts`. A failed lookup reports the failure and keeps the previously
stored mapping.
## Verify what is on disk
The details shown by the application normally match the last successful save,
except for the known persistence cases below. To discard in-memory state and
read `/etc/hosts` again, press `Ctrl+R`.
You can also inspect the file from another terminal:
```bash
sed -n '1,240p' /etc/hosts
```
Remember that saving is normalized rather than byte-preserving. The serializer
aligns fields, groups comments, removes original blank-line placement, and adds
a management header while retaining the modeled Host Entries and comments.
## Persistence and recovery
### What saves immediately
Adding, editing, deleting, moving, activating, deactivating, and successfully
refreshing DNS Entries normally save immediately. `Ctrl+S` explicitly saves the
entire current in-memory Hosts File.
### What does not save immediately
Undo and redo currently update the in-memory state without saving it. After
`Ctrl+Z` or `Ctrl+Y`, use `Ctrl+S` if you want `/etc/hosts` to match the display.
Reloading or quitting discards that unsaved in-memory result. Leaving
Privileged Mode does not save it: the result remains displayed, but its
undo/redo history is cleared and `/etc/hosts` still differs from the display.
A save failure can also leave the interface changed while the file on disk is
unchanged. Reload with `Ctrl+R` to return the interface to the on-disk state
before attempting another change.
### Locate a Pre-edit Backup
Entering Privileged Mode creates one timestamped Pre-edit Backup below the
operating system's temporary directory. The application does not currently show
this path. Ask Python for the directory used on your system:
```bash
python3 -c 'import tempfile; print(tempfile.gettempdir() + "/hosts-manager-backups")'
```
List the directory printed by that command. Backup files are named
`hosts.backup.<timestamp>`:
```bash
ls -lt /path/printed/by/the/previous/command
```
The application does not remove old Pre-edit Backups, apply retention rules, or
record which file belongs to a later session. Do not choose a backup solely
because it is the newest; inspect its timestamp and contents first.
### Restore manually
Restoration is not available through the TUI. To restore manually:
1. Leave the application or return it to Read-only Mode.
2. Locate and inspect the intended Pre-edit Backup.
3. Copy that explicit file over `/etc/hosts` with sudo.
4. Relaunch the application or press `Ctrl+R` to verify the restored contents.
For example, after replacing the placeholder with the exact file you inspected:
```bash
sudo cp /exact/path/to/hosts.backup.TIMESTAMP /etc/hosts
```
This replaces the current Hosts File. Never paste a guessed path or automate
selection of the newest backup without inspecting it.
## Leave Privileged Mode
Press `Ctrl+E` again. The application clears its undo/redo history and forgets
which Pre-edit Backup belongs to the session. The backup file remains in the
temporary directory.
Leaving Privileged Mode currently runs `sudo -k`, which invalidates your cached
sudo timestamp for other terminal sessions as well. This behavior is
[tracked for removal](https://git.s1q.dev/phg/hosts/issues/4).
## Configuration
Press `c` to open configuration. The supported screen controls whether Default
Entries are visible; they are hidden by default. The setting is stored at
`~/.config/hosts-manager/config.json`.
Other values may exist in that file but are internal until their behavior is
reachable through the TUI. Editing them manually is not a supported workflow.
## Troubleshooting
### Privileged Mode asks for a password
This is expected when no cached sudo authorization is available. Canceling the
password prompt leaves the application in Read-only Mode.
### Privileged Mode cannot be enabled
The application requires all three safety gates: valid sudo authorization,
write access to `/etc/hosts`, and successful creation of a Pre-edit Backup. A
failure in any gate leaves mutations disabled. Read the status message for the
specific failure.
### The display differs from `/etc/hosts`
This can occur after undo, redo, a failed save, or sorting followed by other
actions. Press `Ctrl+R` to discard in-memory state and reload the file. If an
unwanted change reached disk, follow the manual restoration procedure above.
### DNS resolution fails
DNS resolution uses the operating system resolver and a per-query timeout. A
failure can result from an invalid DNS name, resolver failure, timeout, or no
address being returned. The application reports the failure rather than
continually retrying it.
## Key reference
### General
| Key | Action |
| --- | --- |
| `Up` / `Down` | Select a Host Entry |
| `Home` / `End` | Select the first or last visible Host Entry |
| `Page Up` / `Page Down` | Move by one page |
| `i` | Sort by IP address |
| `h` | Sort by Canonical Hostname |
| `Ctrl+R` | Reload `/etc/hosts` |
| `c` | Open configuration |
| `?` | Show help |
| `q` or `Ctrl+C` | Quit |
| `Ctrl+E` | Enter or leave Privileged Mode |
### Privileged Mode
| Key | Action |
| --- | --- |
| `n` | Add a Host Entry |
| `e` | Open the selected Host Entry in the Entry Editor |
| `d` | Delete the selected Host Entry |
| `Space` | Activate or deactivate the selected Host Entry |
| `Shift+Up` / `Shift+Down` | Move the selected Host Entry |
| `r` | Refresh the selected DNS Entry |
| `Shift+R` | Refresh all DNS Entries |
| `Ctrl+Z` / `Ctrl+Y` | Undo or redo in memory |
| `Ctrl+S` | Save the current in-memory state |
Within the Entry Editor, use `Tab` and `Shift+Tab` to move between fields and
`Escape` to leave the form. If values changed, the application asks whether to
save, discard, or continue editing.