mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
WIP feature branch: NNTPS news server, per-member Forgejo accounts on email confirm, and founding-lifetime pricing tier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package news
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
nntpclient "github.com/dustin/go-nntp/client"
|
|
"github.com/profullstack/agentbbs/internal/store"
|
|
)
|
|
|
|
// startServer brings up a news Server over an ephemeral loopback listener and
|
|
// returns its address. The server stops when the test ends.
|
|
func startServer(t *testing.T) (string, store.Store) {
|
|
t.Helper()
|
|
st, err := store.Open(filepath.Join(t.TempDir(), "t.db"))
|
|
if err != nil {
|
|
t.Fatalf("open store: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = st.Close() })
|
|
|
|
ns := New(st, "news.test")
|
|
if err := ns.SeedGroups(nil); err != nil {
|
|
t.Fatalf("seed: %v", err)
|
|
}
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
go func() { _ = ns.Serve(ctx, ln) }()
|
|
return ln.Addr().String(), st
|
|
}
|
|
|
|
func TestServerEndToEnd(t *testing.T) {
|
|
addr, st := startServer(t)
|
|
if _, err := st.EnsureUser("alice", "member", "SHA256:a"); err != nil {
|
|
t.Fatalf("ensure user: %v", err)
|
|
}
|
|
|
|
// A non-member cannot authenticate.
|
|
if _, err := Dial(addr, "intruder"); err == nil {
|
|
t.Fatal("non-member should not be able to connect")
|
|
}
|
|
|
|
// An unauthenticated raw client must not be able to LIST (members-only).
|
|
raw, err := nntpclient.New("tcp", addr)
|
|
if err != nil {
|
|
t.Fatalf("raw dial: %v", err)
|
|
}
|
|
if _, err := raw.List("active"); err == nil {
|
|
t.Fatal("unauthenticated LIST must be refused")
|
|
}
|
|
_ = raw.Close()
|
|
|
|
// A member connects, posts, and reads it back.
|
|
r, err := Dial(addr, "alice")
|
|
if err != nil {
|
|
t.Fatalf("member dial: %v", err)
|
|
}
|
|
defer r.Close()
|
|
|
|
if err := r.Post("pfs.general", "First post", "", "Hello, Usenet.\n"); err != nil {
|
|
t.Fatalf("post: %v", err)
|
|
}
|
|
|
|
groups, err := r.Groups()
|
|
if err != nil {
|
|
t.Fatalf("groups: %v", err)
|
|
}
|
|
var general bool
|
|
for _, g := range groups {
|
|
if g.Name == "pfs.general" {
|
|
general = true
|
|
}
|
|
}
|
|
if !general {
|
|
t.Fatalf("pfs.general not listed: %+v", groups)
|
|
}
|
|
|
|
g, err := r.Select("pfs.general")
|
|
if err != nil {
|
|
t.Fatalf("select: %v", err)
|
|
}
|
|
if g.High < 1 {
|
|
t.Fatalf("expected at least one article, high=%d", g.High)
|
|
}
|
|
|
|
ov, err := r.Overview(g.Low, g.High)
|
|
if err != nil {
|
|
t.Fatalf("overview: %v", err)
|
|
}
|
|
if len(ov) != 1 || ov[0].Subject != "First post" {
|
|
t.Fatalf("overview wrong: %+v", ov)
|
|
}
|
|
// From is stamped to the member, not anything client-supplied.
|
|
if !strings.Contains(ov[0].From, "alice") {
|
|
t.Fatalf("From not stamped: %q", ov[0].From)
|
|
}
|
|
|
|
art, err := r.Article(ov[0].Num)
|
|
if err != nil {
|
|
t.Fatalf("article: %v", err)
|
|
}
|
|
if !strings.Contains(art, "Hello, Usenet.") || !strings.Contains(art, "Subject: First post") {
|
|
t.Fatalf("article body/headers wrong: %q", art)
|
|
}
|
|
}
|