Add Go/Postgres admin APIs, Angular admin UI, manual build flow, asset uploads, markdown import/export, configurable slug generation, and the Yar reading theme. Exclude local docs and generated development artifacts from version control.
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package postimport
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeLegacyTime(t *testing.T) {
|
|
got, err := normalizeLegacyTime("2026-01-13 01:25:27.486491+00")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "2026-01-13T01:25:27Z" {
|
|
t.Fatalf("normalized time = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestArticleToPostFallbackSlugAndBody(t *testing.T) {
|
|
root := t.TempDir()
|
|
postsDir := filepath.Join(root, defaultPostsDir)
|
|
if err := os.MkdirAll(postsDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
post, skipped, err := articleToPost(root, postsDir, csvArticle{
|
|
Title: "Hello World",
|
|
BodyMD: "\nBody\n",
|
|
Status: "published",
|
|
CreatedAt: "2026-01-13 01:25:27.486491+00",
|
|
UpdatedAt: "2026-01-13 01:25:27.486491+00",
|
|
PublishedAt: "2026-01-13 01:25:27.486491+00",
|
|
Type: "post",
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if skipped {
|
|
t.Fatal("expected importable post")
|
|
}
|
|
if post.Frontmatter.Slug != "hello-world" {
|
|
t.Fatalf("slug = %q", post.Frontmatter.Slug)
|
|
}
|
|
if post.Body != "Body\n" {
|
|
t.Fatalf("body = %q", post.Body)
|
|
}
|
|
if post.Frontmatter.PublishedAt == nil || *post.Frontmatter.PublishedAt != "2026-01-13T01:25:27Z" {
|
|
t.Fatalf("published_at = %#v", post.Frontmatter.PublishedAt)
|
|
}
|
|
}
|
|
|
|
func TestArticleToPostSkipsExistingWithoutOverwrite(t *testing.T) {
|
|
root := t.TempDir()
|
|
postsDir := filepath.Join(root, defaultPostsDir)
|
|
if err := os.MkdirAll(postsDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(postsDir, "smoking.md"), []byte("existing"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, skipped, err := articleToPost(root, postsDir, csvArticle{
|
|
Slug: "smoking",
|
|
Title: "抽烟",
|
|
BodyMD: "Body",
|
|
Status: "published",
|
|
CreatedAt: "2026-01-13 01:25:27.486491+00",
|
|
UpdatedAt: "2026-01-13 01:25:27.486491+00",
|
|
Type: "post",
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !skipped {
|
|
t.Fatal("expected existing file to be skipped")
|
|
}
|
|
}
|
|
|
|
func TestImportWritesMarkdown(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(root, "backend", "cmd", "osaetctl"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(root, "frontend", "site"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "frontend", "site", "package.json"), []byte("{}"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
csvPath := filepath.Join(root, "articles.csv")
|
|
csvContent := strings.Join([]string{
|
|
"id,slug,title,body_md,body_html,status,archive_id,author_id,published_at,created_at,updated_at,type",
|
|
"post-1,test-post,Test Post,\"Line 1\n\nLine 2\",,published,,,2026-01-13 01:25:27.486491+00,2026-01-13 01:25:27.486491+00,2026-01-13 01:25:27.486491+00,post",
|
|
}, "\n")
|
|
if err := os.WriteFile(csvPath, []byte(csvContent), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := Import(Options{CSVPath: csvPath, PostsDir: defaultPostsDir, WorkingDir: root})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Imported != 1 || result.SkippedExisting != 0 || result.SkippedNonPost != 0 {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
|
|
data, err := os.ReadFile(filepath.Join(root, defaultPostsDir, "test-post.md"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
content := string(data)
|
|
for _, want := range []string{
|
|
"slug: test-post",
|
|
"title: Test Post",
|
|
"status: published",
|
|
"published_at: \"2026-01-13T01:25:27Z\"",
|
|
"Line 1",
|
|
"Line 2",
|
|
} {
|
|
if !strings.Contains(content, want) {
|
|
t.Fatalf("expected output to contain %q\n%s", want, content)
|
|
}
|
|
}
|
|
}
|