Initial commit
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||
|
||||
package payloads
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/volumen/internal/frontmatter"
|
||||
"sourcedock.dev/petrbalvin/volumen/internal/post"
|
||||
)
|
||||
|
||||
const refsPost = `+++
|
||||
title = "Cite"
|
||||
slug = "cite"
|
||||
date = 2026-08-18
|
||||
|
||||
[[refs]]
|
||||
title = "Observational evidence from supernovae"
|
||||
authors = [{ name = "Riess, A. G.", orcid = "0000-0002-1825-0097" }]
|
||||
venue = "The Astronomical Journal"
|
||||
year = 1998
|
||||
doi = "10.1103/PhysRevD.59.103502"
|
||||
+++
|
||||
|
||||
Tvrzení [1].
|
||||
`
|
||||
|
||||
func TestDetailCarriesReferences(t *testing.T) {
|
||||
p := parsePost(t, refsPost)
|
||||
d, err := BuildDetail(p, "https://example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDetail: %v", err)
|
||||
}
|
||||
if len(d.References) != 1 {
|
||||
t.Fatalf("references = %d", len(d.References))
|
||||
}
|
||||
ref := d.References[0]
|
||||
if ref.Title == "" || ref.DOI != "10.1103/PhysRevD.59.103502" ||
|
||||
ref.Authors[0].ORCID != "0000-0002-1825-0097" {
|
||||
t.Fatalf("reference = %+v", ref)
|
||||
}
|
||||
// The JSON-LD block cites the work back with author and identifier.
|
||||
for _, want := range []string{
|
||||
`"citation"`, `"ScholarlyArticle"`,
|
||||
`https://doi.org/10.1103/PhysRevD.59.103502`,
|
||||
`https://orcid.org/0000-0002-1825-0097`,
|
||||
} {
|
||||
if !strings.Contains(d.Meta.JSONLD, want) {
|
||||
t.Fatalf("json-ld missing %q:\n%s", want, d.Meta.JSONLD)
|
||||
}
|
||||
}
|
||||
// The HTML carries the linked citation too.
|
||||
if !strings.Contains(d.HTML, `href="#ref-1"`) {
|
||||
t.Fatalf("html missing the citation anchor:\n%s", d.HTML)
|
||||
}
|
||||
}
|
||||
|
||||
// A post without refs omits the member entirely: the wire shape of
|
||||
// every ordinary post is unchanged.
|
||||
func TestDetailOmitsEmptyReferences(t *testing.T) {
|
||||
p := parsePost(t, "+++\ntitle = \"Plain\"\nslug = \"plain\"\n+++\nx\n")
|
||||
d, err := BuildDetail(p, "https://example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDetail: %v", err)
|
||||
}
|
||||
if len(d.References) != 0 {
|
||||
t.Fatalf("references = %v", d.References)
|
||||
}
|
||||
if strings.Contains(d.Meta.JSONLD, "citation") {
|
||||
t.Fatalf("plain post gained citations:\n%s", d.Meta.JSONLD)
|
||||
}
|
||||
}
|
||||
|
||||
// When the cited work is published in the same instance, the detail
|
||||
// exposes the internal link in every shape the consumer reads: the
|
||||
// rendered HTML, the references member, and the JSON-LD citation.
|
||||
func TestReferencesCarryInternalLink(t *testing.T) {
|
||||
p := parsePost(t, refsPost)
|
||||
p.SetLinkIndex(map[string]string{"10.1103/physrevd.59.103502": "riess-1998"})
|
||||
d, err := BuildDetail(p, "https://example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDetail: %v", err)
|
||||
}
|
||||
if d.References[0].Internal != "/api/volumen/posts/riess-1998" {
|
||||
t.Fatalf("references internal = %+v", d.References[0])
|
||||
}
|
||||
if !strings.Contains(d.HTML, `href="/api/volumen/posts/riess-1998"`) {
|
||||
t.Fatalf("html missing the internal link:\n%s", d.HTML)
|
||||
}
|
||||
if !strings.Contains(d.Meta.JSONLD, "/api/volumen/posts/riess-1998") {
|
||||
t.Fatalf("json-ld missing the internal url:\n%s", d.Meta.JSONLD)
|
||||
}
|
||||
// The canonical DOI identifier stays in the JSON-LD beside it.
|
||||
if !strings.Contains(d.Meta.JSONLD, "https://doi.org/10.1103/PhysRevD.59.103502") {
|
||||
t.Fatalf("canonical identifier lost:\n%s", d.Meta.JSONLD)
|
||||
}
|
||||
}
|
||||
|
||||
func existingWithRefs(t *testing.T) *post.Post {
|
||||
t.Helper()
|
||||
m := frontmatter.NewMeta()
|
||||
m.Set("title", "Hello")
|
||||
m.Set("slug", "hello")
|
||||
// The shape a parsed file carries: an array of tables.
|
||||
m.Set("refs", []map[string]any{
|
||||
{"raw": "Riess, A. G. et al. 1998, AJ 116, 1009"},
|
||||
})
|
||||
return post.New(m, "body")
|
||||
}
|
||||
|
||||
func refTables(t *testing.T, p *post.Post) []map[string]any {
|
||||
t.Helper()
|
||||
raw, ok := p.Metadata.Get("refs")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
list, ok := raw.([]map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("refs = %T, want []map[string]any", raw)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// A form that carries no refs field leaves the stored list untouched: a
|
||||
// save from a page without its bibliography card still round-trips the
|
||||
// file as it stands.
|
||||
func TestRefsAbsentFromFormKeepsTheStoredList(t *testing.T) {
|
||||
p, err := PostFromParams(map[string]string{
|
||||
"title": "Hello", "slug": "hello", "body": "new",
|
||||
}, existingWithRefs(t))
|
||||
if err != nil {
|
||||
t.Fatalf("PostFromParams: %v", err)
|
||||
}
|
||||
tables := refTables(t, p)
|
||||
if len(tables) != 1 || tables[0]["raw"] != "Riess, A. G. et al. 1998, AJ 116, 1009" {
|
||||
t.Fatalf("refs = %v", tables)
|
||||
}
|
||||
}
|
||||
|
||||
// An empty list is an explicit deletion, not a lost field.
|
||||
func TestRefsEmptyListDeletesTheKey(t *testing.T) {
|
||||
p, err := PostFromParams(map[string]string{
|
||||
"title": "Hello", "slug": "hello", "body": "b", "refs": "[]",
|
||||
}, existingWithRefs(t))
|
||||
if err != nil {
|
||||
t.Fatalf("PostFromParams: %v", err)
|
||||
}
|
||||
if _, ok := p.Metadata.Get("refs"); ok {
|
||||
t.Fatal("refs survived an explicit empty list")
|
||||
}
|
||||
}
|
||||
|
||||
// The editor's JSON becomes the frontmatter tables: values trimmed,
|
||||
// identifiers normalised, author ORCIDs kept as name tables, an explicit
|
||||
// number preserved, a blank row dropped.
|
||||
func TestRefsFormWritesCanonicalTables(t *testing.T) {
|
||||
p, err := PostFromParams(map[string]string{
|
||||
"title": "Hello", "slug": "hello", "body": "b",
|
||||
"refs": `[
|
||||
{"raw": " Trimmed raw line. "},
|
||||
{},
|
||||
{"num": 2,
|
||||
"authors": [{"name": "Adam Riess", "orcid": "0000-0002-1825-0097"}, "plain author"],
|
||||
"title": "Observational Evidence from Supernovae",
|
||||
"venue": "The Astronomical Journal", "year": "1998",
|
||||
"volume": "116", "pages": "1009",
|
||||
"doi": "https://doi.org/10.1086/300499",
|
||||
"arxiv": "arXiv:astro-ph/9805201",
|
||||
"url": "https://example.com/paper"}
|
||||
]`,
|
||||
}, existingWithRefs(t))
|
||||
if err != nil {
|
||||
t.Fatalf("PostFromParams: %v", err)
|
||||
}
|
||||
tables := refTables(t, p)
|
||||
if len(tables) != 2 {
|
||||
t.Fatalf("tables = %v", tables)
|
||||
}
|
||||
if tables[0]["raw"] != "Trimmed raw line." {
|
||||
t.Fatalf("raw = %q", tables[0]["raw"])
|
||||
}
|
||||
second := tables[1]
|
||||
if second["num"] != int64(2) {
|
||||
t.Fatalf("num = %v", second["num"])
|
||||
}
|
||||
if second["doi"] != "10.1086/300499" {
|
||||
t.Fatalf("doi = %q", second["doi"])
|
||||
}
|
||||
if second["arxiv"] != "astro-ph/9805201" {
|
||||
t.Fatalf("arxiv = %q", second["arxiv"])
|
||||
}
|
||||
authors, ok := second["authors"].([]any)
|
||||
if !ok || len(authors) != 2 {
|
||||
t.Fatalf("authors = %v", second["authors"])
|
||||
}
|
||||
first, ok := authors[0].(map[string]any)
|
||||
if !ok || first["name"] != "Adam Riess" || first["orcid"] != "0000-0002-1825-0097" {
|
||||
t.Fatalf("author = %v", authors[0])
|
||||
}
|
||||
if authors[1] != "plain author" {
|
||||
t.Fatalf("second author = %v", authors[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefsFormRejectsBadPayloads(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
refs string
|
||||
want string
|
||||
}{
|
||||
{"not a list", `{"raw": "x"}`, "References must be a list of entries."},
|
||||
{"bad doi", `[{"raw": "x", "doi": "not-a-doi"}]`, "Reference 1: DOI must look like 10.xxxx/suffix."},
|
||||
{"bad orcid", `[{"raw": "x", "authors": [{"name": "A", "orcid": "0000-0002-1825-0098"}]}]`,
|
||||
"Reference 1: ORCID must look like 0000-0002-1825-0097."},
|
||||
{"bad url", `[{"raw": "x", "url": "ftp://example.com"}]`, "Reference 1: URL must be an http(s) address."},
|
||||
{"bad arxiv", `[{"raw": "x", "arxiv": "not an id"}]`, "Reference 1: arXiv must be the bare identifier"},
|
||||
{"no citation", `[{"doi": "10.1086/300499"}]`, "Reference 1 needs the citation itself"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := PostFromParams(map[string]string{
|
||||
"title": "Hello", "slug": "hello", "body": "b", "refs": tc.refs,
|
||||
}, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("err = %v, want %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user