test(nano): cover the nano delivery path and its loopback default

felis nano serves the same hasJoined handler as felis api, but behind
nanoStubRepo, which implements only the bar-list lookup and embeds a nil
Repo for everything else. Only the full-api path was tested, against a
complete fake store, so a second store call added to handleHasJoined
would pass CI and panic on every nano login. The loopback default of
-listen, the one thing keeping nano from being an open auth relay, was
not pinned either.

The default moves into a nanoDefaultListen constant, and two tests
cover the path. One serves a login through api.HasJoinedHandler with
nanoStubRepo and a fake identity source and expects the profile back.
The other requires the default to parse as a loopback IP. Taking the
bar-list method off the stub makes the first panic on the nil Repo;
defaulting to 0.0.0.0:8081 or :8081 fails the second.
This commit is contained in:
flyemoji committed 2026-09-22 13:37:51 +09:00
1 parent e0ad78af98
commit 59ec23d4a8
2 files changed
+36 -5

No files matched your search

+29
View File
@@ -14,6 +14,8 @@ import (
"testing"
"time"
"unicode/utf8"
"felis.lolicon.best/internal/api"
)
// [server] listen in a nano config reads like the bind address but is not one; nano must
@@ -85,6 +87,33 @@ func TestNanoDrainsInFlightLoginOnShutdown(t *testing.T) {
}
}
// The nano delivery path: the shared handler behind nano's stub store must admit a login its
// source validated. nanoStubRepo implements only the bar-list lookup, so a new store call in
// handleHasJoined would reach its nil embedded Repo and panic here, while the full-api tests,
// which use a complete fake store, stay green.
func TestNanoAdmitsAValidatedLogin(t *testing.T) {
const id = "069a79f444e94726a5befca90e38aaf5"
ygg := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `{"id":"`+id+`","name":"Notch"}`)
}))
defer ygg.Close()
h := api.HasJoinedHandler([]api.AuthSource{{Tag: "mojang", URL: ygg.URL, Identity: true}}, nanoStubRepo{})
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/session/minecraft/hasJoined?username=Notch&serverId=abc", nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), id) {
t.Fatalf("code = %d body = %q, want the validated profile", w.Code, w.Body.String())
}
}
// An unauthenticated relay on a public address spends this host's Mojang rate limit for
// anyone who finds it, so the default bind has to stay loopback.
func TestNanoListensOnLoopbackByDefault(t *testing.T) {
host, _, err := net.SplitHostPort(nanoDefaultListen)
if ip := net.ParseIP(host); err != nil || ip == nil || !ip.IsLoopback() {
t.Fatalf("default -listen %q is not a loopback address", nanoDefaultListen)
}
}
// The request log prints text the caller chose. A bidi override must not reorder the line,
// an invalid byte must not make journald store the entry as a blob, and a huge query must
// not become a huge log line. serverId is left out so the handler answers without asking