summaryrefslogtreecommitdiff
path: root/internal/web/server_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 14:02:54 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 14:02:54 +0200
commit0586599fc6020df996c4278230eeea99b9d070cb (patch)
treea03b615039243f84fa2d69430b769676d1da9135 /internal/web/server_test.go
parent44472bbe31475a3c672ae003d928d7caffb4b50a (diff)
downloadlectio-0586599fc6020df996c4278230eeea99b9d070cb.tar.gz
lectio-0586599fc6020df996c4278230eeea99b9d070cb.zip
licence: relicense MIT -> AGPL-3.0-or-later
lectio was MIT, which let anyone take it closed. The concern is not people selling it -- no licence stops that, and the AGPL does not try to -- but someone building a proprietary product on it and giving nothing back. Plain GPL would leave the obvious hole open: lectio-web is a network service, and hosting is not distribution, so a modified lectio-web could be run as a paid subscription API without ever publishing a line. AGPL section 13 closes exactly that. LICENSE is the verbatim FSF text. README carries the standard notice. Section 13 requires a modified network-reachable version to PROMINENTLY offer its source to the users interacting with it, so the offer ships with the code rather than living only in a file nobody fetches: - GET /source plain text, no template or config dependency, so it answers even when something else is broken - page footers every full page (fragments render inside one) - JSON envelope "source" / "license" - iCal header X-LECTIO-SOURCE / X-LECTIO-LICENSE The feed fields are not redundant: an /api/calendar.json consumer or an .ics subscriber never loads a page, so the footer alone would miss them. config.SourceURL is the single source of truth, and says in its comment that a fork running as a service must repoint it -- an offer that leads to someone else's code is not an offer. Tests pin all of it. This is a licence obligation, not a feature, so it should fail loudly if a later change drops it.
Diffstat (limited to 'internal/web/server_test.go')
-rw-r--r--internal/web/server_test.go49
1 files changed, 49 insertions, 0 deletions
diff --git a/internal/web/server_test.go b/internal/web/server_test.go
index 57286d5..42de0b5 100644
--- a/internal/web/server_test.go
+++ b/internal/web/server_test.go
@@ -630,3 +630,52 @@ func TestCalendarPDF(t *testing.T) {
t.Errorf("not a pdf (len=%d)", len(b))
}
}
+
+// TestSourceOffer covers the AGPL-3.0 §13 obligation: a user interacting with
+// lectio-web over a network must be offered the corresponding source. That is
+// a licence term, not a feature, so it is pinned here -- if someone later
+// deletes the footer or the route, this fails rather than quietly putting the
+// operator out of compliance.
+func TestSourceOffer(t *testing.T) {
+ cfg := config.Default()
+ cfg.Offline = true // no network; the offer must not depend on readings
+ srv := NewServer(cfg)
+
+ t.Run("/source names the licence and where to get the code", func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ srv.ServeHTTP(rec, httptest.NewRequest("GET", "/source", nil))
+ if rec.Code != http.StatusOK {
+ t.Fatalf("status = %d, want 200", rec.Code)
+ }
+ if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") {
+ t.Errorf("content-type = %q, want text/plain", ct)
+ }
+ body := rec.Body.String()
+ for _, want := range []string{config.SourceURL, config.License, config.Version} {
+ if !strings.Contains(body, want) {
+ t.Errorf("body missing %q:\n%s", want, body)
+ }
+ }
+ })
+
+ // Every page a user can actually land on carries the link. Fragments
+ // (htmx partials) are excluded -- they render inside a full page that
+ // already has it.
+ for _, path := range []string{
+ "/?date=2026-07-22&v=wuj",
+ "/reader?v=wuj&book=John&chapter=1",
+ "/settings",
+ "/bookmarks",
+ } {
+ t.Run("footer on "+path, func(t *testing.T) {
+ rec := httptest.NewRecorder()
+ srv.ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
+ if rec.Code != http.StatusOK {
+ t.Fatalf("status = %d, want 200", rec.Code)
+ }
+ if !strings.Contains(rec.Body.String(), `href="/source"`) {
+ t.Errorf("page has no source link (AGPL §13):\n%s", rec.Body.String())
+ }
+ })
+ }
+}