package main import ( "bytes" "context" "database/sql" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" ) func testApplication(t *testing.T) *application { t.Helper() store, err := openPlaygroundStore(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { store.Close() }) monaco := filepath.Join(t.TempDir(), "monaco") if err := os.MkdirAll(monaco, 0o755); err != nil { t.Fatal(err) } app, err := newApplication(store, map[string]memoryAsset{ "core.js": { contentType: "text/javascript; charset=utf-8", content: []byte("export const ready = true"), etag: `"core"`, modified: time.Unix(0, 0), }, }, monaco) if err != nil { t.Fatal(err) } return app } func TestPackageIsServedFromMemory(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/pkg/core.js", nil) response := httptest.NewRecorder() testApplication(t).routes().ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("status = %d", response.Code) } if response.Body.String() != "export const ready = true" { t.Fatalf("body = %q", response.Body.String()) } if response.Header().Get("Cross-Origin-Opener-Policy") != "same-origin" { t.Fatal("cross-origin isolation header is missing") } if response.Header().Get("Access-Control-Allow-Origin") != "*" { t.Fatal("package CORS header is missing") } if response.Header().Get("Cross-Origin-Resource-Policy") != "cross-origin" { t.Fatal("package resource policy is missing") } } func TestHealth(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/healthz", nil) response := httptest.NewRecorder() testApplication(t).routes().ServeHTTP(response, request) if response.Code != http.StatusNoContent { t.Fatalf("status = %d", response.Code) } } func TestHomeLogsDeploymentMetadata(t *testing.T) { t.Setenv("YAWN_DEPLOYED_AT", "2026-08-24T18:30:00Z") t.Setenv("YAWN_DEPLOY_COMMIT_MESSAGE", "Fix Docker deployment") request := httptest.NewRequest(http.MethodGet, "/", nil) response := httptest.NewRecorder() testApplication(t).routes().ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("status = %d", response.Code) } for _, expected := range []string{ `console.log("Yawn deployed at 25 August 2026, 12:00:00 AM IST\nLatest commit: Fix Docker deployment")`, "Yawn", } { if !bytes.Contains(response.Body.Bytes(), []byte(expected)) { t.Fatalf("home page does not contain %q", expected) } } } func TestPlaygroundStoreMigratesLoadouts(t *testing.T) { path := filepath.Join(t.TempDir(), "legacy.db") database, err := sql.Open("sqlite3", path) if err != nil { t.Fatal(err) } if _, err := database.Exec(` CREATE TABLE playgrounds ( id TEXT NOT NULL, revision INTEGER NOT NULL, title TEXT NOT NULL, code TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (id, revision) ) `); err != nil { database.Close() t.Fatal(err) } if err := database.Close(); err != nil { t.Fatal(err) } store, err := openPlaygroundStore(path) if err != nil { t.Fatal(err) } t.Cleanup(func() { store.Close() }) saved, err := store.save(context.Background(), "", "Migrated", "const migrated = true;", "") if err != nil { t.Fatal(err) } if string(saved.Loadouts) != "[]" { t.Fatalf("loadouts = %s", saved.Loadouts) } } func TestSaveAndLoadPlaygroundRevision(t *testing.T) { app := testApplication(t) handler := app.routes() requestBody, _ := json.Marshal(map[string]any{ "title": "First scene", "code": "const scene = true;", "loadouts": []map[string]any{{ "id": "custom", "name": "Custom", "layout": map[string]any{}, }}, }) request := httptest.NewRequest(http.MethodPost, "/api/playgrounds", bytes.NewReader(requestBody)) response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusCreated { t.Fatalf("save status = %d: %s", response.Code, response.Body.String()) } var saved playground if err := json.NewDecoder(response.Body).Decode(&saved); err != nil { t.Fatal(err) } if saved.Revision != 1 || !playgroundIDPattern.MatchString(saved.ID) { t.Fatalf("saved = %#v", saved) } requestBody, _ = json.Marshal(map[string]string{ "id": saved.ID, "title": "Second scene", "code": "const scene = false;", }) request = httptest.NewRequest(http.MethodPost, "/api/playgrounds", bytes.NewReader(requestBody)) response = httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusCreated { t.Fatalf("second save status = %d: %s", response.Code, response.Body.String()) } var second playground if err := json.NewDecoder(response.Body).Decode(&second); err != nil { t.Fatal(err) } if second.ID != saved.ID || second.Revision != 2 { t.Fatalf("second = %#v", second) } request = httptest.NewRequest(http.MethodGet, "/api/playgrounds/"+saved.ID+"/1", nil) response = httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("load status = %d: %s", response.Code, response.Body.String()) } var loaded playground if err := json.NewDecoder(response.Body).Decode(&loaded); err != nil { t.Fatal(err) } if loaded.Code != "const scene = true;" || loaded.Title != "First scene" { t.Fatalf("loaded = %#v", loaded) } var loadouts []struct { ID string `json:"id"` Name string `json:"name"` Layout map[string]any `json:"layout"` } if err := json.Unmarshal(loaded.Loadouts, &loadouts); err != nil { t.Fatal(err) } if len(loadouts) != 1 || loadouts[0].ID != "custom" || loadouts[0].Name != "Custom" || len(loadouts[0].Layout) != 0 { t.Fatalf("loadouts = %s", loaded.Loadouts) } request = httptest.NewRequest(http.MethodGet, "/api/playgrounds/"+saved.ID+"/2", nil) response = httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("second load status = %d: %s", response.Code, response.Body.String()) } if err := json.NewDecoder(response.Body).Decode(&loaded); err != nil { t.Fatal(err) } if loaded.Code != "const scene = false;" || loaded.Title != "Second scene" { t.Fatalf("second loaded = %#v", loaded) } } func TestSavedPlaygroundPageRoute(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/playground/abcdef12/1", nil) response := httptest.NewRecorder() testApplication(t).routes().ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("status = %d", response.Code) } if contentType := response.Header().Get("Content-Type"); contentType != "text/html; charset=utf-8" { t.Fatalf("content type = %q", contentType) } } func TestGltfViewerPageRoute(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/view/gltf", nil) response := httptest.NewRecorder() testApplication(t).routes().ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("status = %d", response.Code) } if contentType := response.Header().Get("Content-Type"); contentType != "text/html; charset=utf-8" { t.Fatalf("content type = %q", contentType) } for _, content := range []string{ `"@yawn/core": "/pkg/core.js"`, `"@yawn/handles": "/pkg/handles.js"`, "Drop a glTF file", } { if !bytes.Contains(response.Body.Bytes(), []byte(content)) { t.Fatalf("viewer page does not contain %q", content) } } }