package webapp import ( "bytes" "context" "errors" "fmt" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func containsString(values []string, target string) bool { for _, value := range values { if value == target { return true } } return false } func TestRuntimeServesEntryWithSecurityHeadersAndNoCookies(t *testing.T) { archive := canvasArchive(t, map[string]string{ "manifest.yaml": staticManifestYAML, "ui/index.html": "safe", }) pkg, err := ValidatePackage(bytes.NewReader(archive)) if err != nil { t.Fatalf("ValidatePackage: %v", err) } artifacts, err := NewArtifactStore(filepath.Join(t.TempDir(), "artifacts")) if err != nil { t.Fatalf("NewArtifactStore: %v", err) } artifact, err := artifacts.Put(pkg) if err != nil { t.Fatalf("Put: %v", err) } manager := NewTokenManager(nil) token, err := manager.Issue(CapabilityBinding{ UserID: "user-1", InstanceID: "instance-1", ReleaseID: "release-1", WebAppKey: "main", Placement: "task-canvas", Artifact: artifact, Entry: "ui/index.html", }, 0) if err != nil { t.Fatalf("Issue: %v", err) } runtime := NewRuntime(manager, artifacts, nil, []string{"http://127.0.0.1:38429", "tauri://localhost", "http://tauri.localhost"}) req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Origin", "null") req.AddCookie(&http.Cookie{Name: "kandev_session", Value: "must-not-be-used"}) response := httptest.NewRecorder() runtime.Serve(response, req, token, "") if response.Code != http.StatusOK { t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) } if !strings.Contains(response.Body.String(), "safe") { t.Fatalf("body = %q", response.Body.String()) } if got := response.Header().Get("Content-Security-Policy"); !strings.Contains(got, "sandbox allow-scripts allow-forms") || !strings.Contains(got, "form-action 'none'") || !strings.Contains(got, "frame-ancestors 'self' http://127.0.0.1:38429") { t.Fatalf("CSP = %q", got) } for key, want := range map[string]string{ "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer", "Cross-Origin-Resource-Policy": "cross-origin", "Access-Control-Allow-Origin": "null", } { if got := response.Header().Get(key); got != want { t.Errorf("%s = %q, want %q", key, got, want) } } if got := response.Header().Get("Set-Cookie"); got != "" { t.Fatalf("runtime set a cookie: %q", got) } } func TestRuntimeResolvesNestedEntryAssetsAndKeepsProtocolRoot(t *testing.T) { archive := canvasArchive(t, map[string]string{ "manifest.yaml": staticManifestYAML, "ui/index.html": ``, "ui/app.js": `fetch("./_kandev/v1/context")`, "ui/app.css": `body { color: red; }`, }) pkg, err := ValidatePackage(bytes.NewReader(archive)) if err != nil { t.Fatalf("ValidatePackage: %v", err) } artifacts, err := NewArtifactStore(filepath.Join(t.TempDir(), "artifacts")) if err != nil { t.Fatalf("NewArtifactStore: %v", err) } artifact, err := artifacts.Put(pkg) if err != nil { t.Fatalf("Put: %v", err) } manager := NewTokenManager(nil) binding := CapabilityBinding{ UserID: "user-1", InstanceID: "instance-1", ReleaseID: "release-1", WebAppKey: "main", Placement: "task-canvas", Artifact: artifact, Entry: "ui/index.html", } token, err := manager.Issue(binding, 0) if err != nil { t.Fatalf("Issue: %v", err) } runtime := NewRuntime(manager, artifacts, nil, nil) runtime.SetProtocolHandler(func(w http.ResponseWriter, _ *http.Request, _ string, _ CapabilityBinding, protocolPath string) { if protocolPath != "v1/context" { t.Errorf("protocol path = %q, want v1/context", protocolPath) } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"protocol":true}`)) }) for requestPath, want := range map[string]string{ "app.js": `fetch("./_kandev/v1/context")`, "app.css": `body { color: red; }`, } { response := httptest.NewRecorder() runtime.Serve(response, httptest.NewRequest(http.MethodGet, "/", nil), token, requestPath) if response.Code != http.StatusOK || response.Body.String() != want { t.Fatalf("%s response = %d %q, want 200 %q", requestPath, response.Code, response.Body.String(), want) } } protocolResponse := httptest.NewRecorder() runtime.Serve(protocolResponse, httptest.NewRequest(http.MethodGet, "/", nil), token, "_kandev/v1/context") if protocolResponse.Code != http.StatusOK || protocolResponse.Body.String() != `{"protocol":true}` { t.Fatalf("protocol response = %d %q", protocolResponse.Code, protocolResponse.Body.String()) } } func TestRuntimeStartupBootstrapPreservesArtifact(t *testing.T) { entry := "\nready" archive := canvasArchive(t, map[string]string{ "manifest.yaml": staticManifestYAML, "ui/index.html": entry, "ui/app.js": "window.__authored = true;", "_kandev/host-runtime.js": "window.__package_shadow = true;", "ui/asset-without-bootstrap.js": "unchanged", }) pkg, err := ValidatePackage(bytes.NewReader(archive)) if err != nil { t.Fatalf("ValidatePackage: %v", err) } artifacts, err := NewArtifactStore(filepath.Join(t.TempDir(), "artifacts")) if err != nil { t.Fatalf("NewArtifactStore: %v", err) } artifact, err := artifacts.Put(pkg) if err != nil { t.Fatalf("Put: %v", err) } manager := NewTokenManager(nil) token, err := manager.Issue(CapabilityBinding{ UserID: "user-1", InstanceID: "instance-1", ReleaseID: "release-1", WebAppKey: "main", Placement: "task-canvas", Artifact: artifact, Entry: "ui/index.html", }, 0) if err != nil { t.Fatalf("Issue: %v", err) } runtime := NewRuntime(manager, artifacts, nil, nil) response := httptest.NewRecorder() runtime.Serve(response, httptest.NewRequest(http.MethodGet, "/", nil), token, "") if response.Code != http.StatusOK { t.Fatalf("entry status = %d, body = %s", response.Code, response.Body.String()) } body := response.Body.String() bootstrap := `` if !strings.Contains(body, bootstrap) { t.Fatalf("entry body does not contain host bootstrap: %q", body) } if strings.Index(body, bootstrap) >= strings.Index(body, `` if !strings.HasPrefix(body, ``) { t.Fatalf("doctype or encoding prefix changed: %q", body) } if strings.Index(body, bootstrap) <= strings.Index(body, "

Hello

") { t.Fatalf("host bootstrap was not appended to the implied body: %q", body) } if got := response.Header().Get("Content-Length"); got != fmt.Sprintf("%d", len(body)) { t.Fatalf("Content-Length = %q, want %d", got, len(body)) } stored, err := os.ReadFile(filepath.Join(artifacts.Path(artifact), "ui", "index.html")) if err != nil { t.Fatalf("ReadFile stored entry: %v", err) } if string(stored) != entry { t.Fatalf("stored entry changed: %q", stored) } } func TestInjectRuntimeBootstrapSkipsTemplateContent(t *testing.T) { entry := `` result, err := injectRuntimeBootstrap([]byte(entry)) if err != nil { t.Fatalf("injectRuntimeBootstrap: %v", err) } body := string(result) bootstrap := `` bootstrapIndex := strings.Index(body, bootstrap) templateEnd := strings.Index(body, "") authoredScript := strings.Index(body, ``, } { if _, err := injectRuntimeBootstrap([]byte(entry)); !errors.Is(err, ErrRuntimeBootstrapUnavailable) { t.Fatalf("injectRuntimeBootstrap(%q) error = %v, want %v", entry, err, ErrRuntimeBootstrapUnavailable) } } } func TestInjectRuntimeBootstrapDoesNotTreatForeignTemplateAsInert(t *testing.T) { entry := `` result, err := injectRuntimeBootstrap([]byte(entry)) if err != nil { t.Fatalf("injectRuntimeBootstrap: %v", err) } body := string(result) bootstrap := `` bootstrapIndex := strings.Index(body, bootstrap) foreignScript := strings.Index(body, ``) if bootstrapIndex < 0 || foreignScript < 0 || bootstrapIndex >= foreignScript { t.Fatalf("host bootstrap was inserted after foreign executable script: %q", body) } } func TestInjectRuntimeBootstrapTracksHTMLTemplateAfterForeignBreakout(t *testing.T) { entry := `

` result, err := injectRuntimeBootstrap([]byte(entry)) if err != nil { t.Fatalf("injectRuntimeBootstrap: %v", err) } body := string(result) bootstrap := `` bootstrapIndex := strings.Index(body, bootstrap) templateScript := strings.Index(body, ``) authoredScript := strings.Index(body, `