Files
aego/tools/gendocs/site/try_examples_test.go
sanspie fac6fa35a4
ci-platforms / darwin arm64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 7m29s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 14m13s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: error
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
fix: stabilize CI and prepare docs release
Keep recovery copies private on Windows, make cross-platform test fixtures deterministic, and preserve the updated Art and documentation contracts.

Refresh workspace screenshot references for the intentional factory layouts and restore the earlier Russian palette reference after before/after comparison. Keep the existing screenshot tolerances.
2026-11-09 16:24:24 +03:00

333 lines
11 KiB
Go

package site
import (
"archive/zip"
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"html"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"aego/asset"
"aego/project"
"aego/project/remoteexample"
)
func sourceExampleFixture() *Export {
e := fixture()
e.Entries[0].Try = "scene version=1 {}"
e.Entries[0].TrySource = &TrySource{
EntryScene: "scenes/main.aescene",
Files: map[string][]byte{
"project.aego": []byte("project version=1 { name=\"Example\" module=\"game\" native=false }"),
"scenes/main.aescene": []byte(e.Entries[0].Try),
"scenes/main.aescene.meta": []byte("meta version=1 { id=\"10000000000000000000000000000001\" importer=\"scene\" }"),
"scripts/demo/main.aes": []byte("package demo\n"),
"assets/light.png": []byte("source pixels"),
},
Modules: []string{"engine", "render2d"},
AssetRefs: []string{"10000000000000000000000000000001"},
}
return e
}
func TestTryLinksDownloadCompleteSourceProjectInBothLanguages(t *testing.T) {
e := sourceExampleFixture()
dir := t.TempDir()
_, err := Build(e, dir, Options{Playground: true, ExamplesURL: "https://docs.example/aego/", ExamplesVersion: "1.2.3", ExamplesLicense: "NOASSERTION", Commit: "source-snapshot"})
if err != nil {
t.Fatal(err)
}
manifests, err := filepath.Glob(filepath.Join(dir, "examples", "*", "manifest.json"))
if err != nil || len(manifests) != 1 {
t.Fatal("complete source package missing", manifests, err)
}
manifest, err := os.ReadFile(manifests[0])
if err != nil {
t.Fatal(err)
}
rel, _ := filepath.Rel(dir, manifests[0])
manifestURL := "https://docs.example/aego/" + filepath.ToSlash(rel)
m, err := remoteexample.ParseManifest(manifest, manifestURL, remoteexample.Limits{})
if err != nil || m.Version != "1.2.3" || len(m.Modules) != 2 || len(m.AssetRefs) != 1 {
t.Fatal("bad example manifest", m, err)
}
archives, _ := filepath.Glob(filepath.Join(filepath.Dir(manifests[0]), "project-*.zip"))
if len(archives) != 1 {
t.Fatal("content-addressed source archive missing")
}
data, _ := os.ReadFile(archives[0])
if got := fmt.Sprintf("%x", sha256.Sum256(data)); got != m.Archive.SHA256 {
t.Fatal("archive differs from manifest")
}
z, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
t.Fatal(err)
}
seen := map[string]bool{}
buildBytes, err := os.ReadFile(filepath.Join(dir, "docs-build.json"))
if err != nil {
t.Fatal(err)
}
var build struct {
BuildID string `json:"buildID"`
RegistrySHA256 string `json:"registrySHA256"`
}
if err := json.Unmarshal(buildBytes, &build); err != nil {
t.Fatal(err)
}
for _, f := range z.File {
r, err := f.Open()
if err != nil {
t.Fatal(err)
}
b, err := io.ReadAll(r)
r.Close()
if err != nil {
t.Fatal(err)
}
if want, ok := e.Entries[0].TrySource.Files[f.Name]; ok && !bytes.Equal(b, want) {
t.Fatalf("source was changed: %s", f.Name)
}
if f.Name == "EXAMPLE-NOTICE.txt" && (!bytes.Contains(b, []byte("NOASSERTION")) || !bytes.Contains(b, []byte("source-snapshot"))) {
t.Fatal("unknown license/source provenance hidden")
}
if f.Name == "EXAMPLE-NOTICE.txt" && (!bytes.Contains(b, []byte("Documentation build: "+build.BuildID)) || !bytes.Contains(b, []byte("Registry SHA-256: "+build.RegistrySHA256))) {
t.Fatal("example notice is not bound to the documentation registry/build")
}
seen[f.Name] = true
}
for name := range e.Entries[0].TrySource.Files {
if !seen[name] {
t.Fatalf("source dependency missing from package: %s", name)
}
}
linkRE := regexp.MustCompile(`class="try-open button" href="([^"]+)"`)
for _, page := range []string{"welcome.html", "ru/welcome.html"} {
b, err := os.ReadFile(filepath.Join(dir, page))
if err != nil {
t.Fatal(err)
}
match := linkRE.FindSubmatch(b)
if match == nil {
t.Fatalf("%s has no genuine Open in Aego link", page)
}
link, err := remoteexample.ParseLink(html.UnescapeString(string(match[1])))
if err != nil || link.ManifestURL != manifestURL || link.Mode != "scene" {
t.Fatal("page deep link does not match the complete source manifest", link, err)
}
for _, marker := range []string{`class="try-project button"`, ` download`, `class="try-download"`, `data-example-archive=`} {
if !bytes.Contains(b, []byte(marker)) {
t.Fatalf("page omits full project fallback or edited scene export: %s", marker)
}
}
}
}
func TestProductionTryArchiveIncludesReviewedLicenseText(t *testing.T) {
const reviewed = "MIT License\n\nCopyright (c) 2026 Example owner\n\nReviewed terms.\n"
dir := t.TempDir()
_, err := Build(sourceExampleFixture(), dir, Options{
ExamplesURL: "https://docs.example/", ExamplesLicense: "MIT",
ExamplesLicenseText: reviewed, ExamplesProduction: true,
})
if err != nil {
t.Fatal(err)
}
manifests, err := filepath.Glob(filepath.Join(dir, "examples", "*", "manifest.json"))
if err != nil || len(manifests) != 1 {
t.Fatalf("production manifest missing: %v %v", manifests, err)
}
manifest, err := os.ReadFile(manifests[0])
if err != nil {
t.Fatal(err)
}
var metadata struct {
License string `json:"license"`
}
if err := json.Unmarshal(manifest, &metadata); err != nil || metadata.License != "MIT" {
t.Fatalf("production manifest lost the reviewed license: %+v %v", metadata, err)
}
archives, err := filepath.Glob(filepath.Join(dir, "examples", "*", "project-*.zip"))
if err != nil || len(archives) != 1 {
t.Fatalf("production archive missing: %v %v", archives, err)
}
z, err := zip.OpenReader(archives[0])
if err != nil {
t.Fatal(err)
}
defer z.Close()
var terms, notice []byte
for _, file := range z.File {
if file.Name != "AEGO-EXAMPLE-LICENSE.txt" && file.Name != "EXAMPLE-NOTICE.txt" {
continue
}
r, err := file.Open()
if err != nil {
t.Fatal(err)
}
data, err := io.ReadAll(r)
r.Close()
if err != nil {
t.Fatal(err)
}
if file.Name == "AEGO-EXAMPLE-LICENSE.txt" {
terms = data
} else {
notice = data
}
}
if string(terms) != reviewed || !bytes.Contains(notice, []byte("License: MIT\n")) || !bytes.Contains(notice, []byte("AEGO-EXAMPLE-LICENSE.txt")) {
t.Fatal("archive lost the reviewed license text or its notice")
}
}
func TestReviewedExampleLicenseContainsMITTermsAndAttribution(t *testing.T) {
text, err := os.ReadFile(filepath.Join("..", "..", "..", "docs", "try-examples-license.txt"))
if err != nil {
t.Fatal(err)
}
for _, required := range []string{
"MIT License", "Copyright (c) 2026 Alexander Karpov",
"Permission is hereby granted, free of charge", "The above copyright notice and this permission notice",
`THE SOFTWARE IS PROVIDED "AS IS"`,
} {
if !bytes.Contains(text, []byte(required)) {
t.Errorf("reviewed example license is missing %q", required)
}
}
}
func TestTrySourcePackagingFailsOnMissingDependenciesAndUnknownProductionLicense(t *testing.T) {
opt := Options{ExamplesURL: "https://docs.example/"}
for _, test := range []struct {
name string
change func(*Export, *Options)
}{
{"missing source", func(e *Export, _ *Options) { e.Entries[0].TrySource = nil }},
{"missing entry", func(e *Export, _ *Options) { delete(e.Entries[0].TrySource.Files, "scenes/main.aescene") }},
{"unsafe file", func(e *Export, _ *Options) { e.Entries[0].TrySource.Files["../outside.txt"] = []byte("bad") }},
{"unknown production license", func(_ *Export, o *Options) { o.ExamplesProduction = true }},
{"missing production license text", func(_ *Export, o *Options) { o.ExamplesLicense, o.ExamplesProduction = "MIT", true }},
{"license path collision", func(e *Export, o *Options) {
e.Entries[0].TrySource.Files["aego-example-license.TXT"] = []byte("existing source")
o.ExamplesLicenseText = "reviewed terms"
}},
{"invalid license text", func(_ *Export, o *Options) { o.ExamplesLicenseText = string([]byte{0xff}) }},
{"blank license text", func(_ *Export, o *Options) { o.ExamplesLicenseText = "\n\t" }},
{"disabled production examples", func(_ *Export, o *Options) {
o.ExamplesURL, o.ExamplesLicense, o.ExamplesProduction = "", "LicenseRef-Reviewed-Test-Example", true
}},
{"insecure origin", func(_ *Export, o *Options) { o.ExamplesURL = "http://docs.example/" }},
} {
t.Run(test.name, func(t *testing.T) {
e, o := sourceExampleFixture(), opt
test.change(e, &o)
if _, err := Build(e, t.TempDir(), o); err == nil {
t.Fatal("invalid official source package accepted")
}
})
}
}
func TestTrySourceHostCreatesProjectUsingCanonicalEncoder(t *testing.T) {
e := sourceExampleFixture()
s := e.Entries[0].TrySource
delete(s.Files, project.FileName)
dir := t.TempDir()
if _, err := Build(e, dir, Options{ExamplesURL: "https://docs.example/"}); err != nil {
t.Fatal(err)
}
archives, err := filepath.Glob(filepath.Join(dir, "examples", "*", "project-*.zip"))
if err != nil || len(archives) != 1 {
t.Fatal("source package missing", archives, err)
}
r, err := zip.OpenReader(archives[0])
if err != nil {
t.Fatal(err)
}
defer r.Close()
root := t.TempDir()
found := false
for _, f := range r.File {
if f.Name != project.FileName {
continue
}
stream, err := f.Open()
if err != nil {
t.Fatal(err)
}
data, err := io.ReadAll(stream)
stream.Close()
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, project.FileName), data, 0644); err != nil {
t.Fatal(err)
}
found = true
}
if !found {
t.Fatal("packaging host omitted project.aego")
}
p, err := project.Load(root)
if err != nil {
t.Fatal(err)
}
meta, err := asset.ParseSourceMeta(s.Files[s.EntryScene+".meta"], s.EntryScene+".meta")
if err != nil {
t.Fatal(err)
}
if p.MainScene != meta.ID || p.Module != "game" || p.Native || p.Window.Width != 960 || p.Window.Height != 540 || p.Name != e.Entries[0].Title {
t.Fatalf("host changed the existing entry identity or produced an incompatible project: %+v", p)
}
if _, exists := s.Files[project.FileName]; exists {
t.Fatal("packaging mutated the registry source snapshot")
}
}
func TestRealExportHasCompleteSourcePackageForEveryPublicComponent(t *testing.T) {
file := os.Getenv("AEGO_DOCS_EXPORT")
if file == "" {
t.Skip("run make docs-check to supply the current real registry export")
}
e, err := Load(file)
if err != nil {
t.Fatal(err)
}
count := 0
for _, entry := range e.Entries {
if entry.Component == nil {
continue
}
count++
if entry.TrySource == nil || entry.TrySource.EntryScene == "" || len(entry.TrySource.Files) == 0 {
t.Errorf("%s has no complete editable source project", entry.ID)
}
}
if count == 0 {
t.Fatal("no public component registry entries tested")
}
dir := t.TempDir()
if _, err := Build(e, dir, Options{ExamplesURL: "https://docs.example/", ExamplesVersion: "1.0.0"}); err != nil {
t.Fatal(err)
}
files, err := filepath.Glob(filepath.Join(dir, "examples", "*", "manifest.json"))
if err != nil || len(files) < count {
t.Fatalf("source packages=%d public components=%d err=%v", len(files), count, err)
}
t.Logf("%d public components have validated complete source packages (%d packages including guides)", count, len(files))
for _, file := range files {
b, _ := os.ReadFile(file)
if strings.Contains(string(b), ".git/") || strings.Contains(string(b), "native/") {
t.Fatalf("package included excluded repository data: %s", file)
}
}
}