Run pinned Endoleap headless runtime against real HTTP and PostgreSQL in CI
This commit is contained in:
@@ -69,8 +69,19 @@ jobs:
|
||||
print("Negative control result:", control if control in known else "unclassified")
|
||||
sys.exit("Slack client preflight could not verify the configured credentials")
|
||||
PYSLACK
|
||||
- name: Verify schema migration in an isolated database
|
||||
run: bash deploy/check-schema.sh
|
||||
- name: Fetch pinned headless HTTP integration client
|
||||
env:
|
||||
HEADLESS_CLIENT_REVISION: 3c5e67c03d944828d0c2e51ecadb4d2dac1c9765
|
||||
run: |
|
||||
set -eu
|
||||
mkdir -p .headless-client
|
||||
curl --fail --silent --show-error --location \
|
||||
"https://gitea.entroia.com/Zayden-Jung/Endoleap/archive/$HEADLESS_CLIENT_REVISION.tar.gz" \
|
||||
| tar -xz --strip-components=1 -C .headless-client
|
||||
test -f .headless-client/tests/headless/foundry.integration.mjs
|
||||
printf 'Pinned Endoleap client: %s\n' "$HEADLESS_CLIENT_REVISION"
|
||||
- name: Verify schema migration and real headless HTTP client in an isolated database
|
||||
run: HEADLESS_CLIENT_SOURCE="$PWD/.headless-client" bash deploy/check-schema.sh
|
||||
- name: Smoke test HTTP routes
|
||||
env:
|
||||
PUBLIC_ORIGIN: https://api.example.com
|
||||
|
||||
@@ -78,3 +78,18 @@ for package in platform auth research; do
|
||||
-e RESEARCH_TEST_DATABASE_URL=postgres://foundry_research_fixture@127.0.0.1:5432/postgres?sslmode=disable \
|
||||
--entrypoint /opt/integration-tests postgres:17-alpine -test.v
|
||||
done
|
||||
|
||||
# Run the actual pinned Endoleap runtime against this build's authenticated HTTP
|
||||
# routes and the same disposable database. Only this remote container needs Node.
|
||||
if [[ -n "${HEADLESS_CLIENT_SOURCE:-}" ]]; then
|
||||
test -f "$HEADLESS_CLIENT_SOURCE/tests/headless/foundry.integration.mjs"
|
||||
docker run --rm --network "container:$container" --user 1000:1000 \
|
||||
--read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=32m,mode=1777 \
|
||||
--mount "type=bind,source=$(pwd)/bin/platform-tests,target=/opt/integration-tests,readonly" \
|
||||
--mount "type=bind,source=$HEADLESS_CLIENT_SOURCE,target=/opt/headless-client,readonly" \
|
||||
-e PLATFORM_TEST_DATABASE_URL=postgres://foundry_fixture@127.0.0.1:5432/postgres?sslmode=disable \
|
||||
-e FOUNDRY_HEADLESS_SOURCE=/opt/headless-client \
|
||||
--entrypoint /opt/integration-tests node:24-bookworm-slim \
|
||||
-test.v -test.run '^TestHeadlessClientHTTPIntegration$' -test.timeout 3m
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Runs the real Endoleap runtime against authenticated Foundry HTTP and PostgreSQL.
|
||||
// This opt-in is enabled by the existing remote schema runner, never local builds.
|
||||
func TestHeadlessClientHTTPIntegration(t *testing.T) {
|
||||
source, dsn := os.Getenv("FOUNDRY_HEADLESS_SOURCE"), os.Getenv("PLATFORM_TEST_DATABASE_URL")
|
||||
if source == "" {
|
||||
t.Skip("requires remote headless checkout")
|
||||
}
|
||||
if dsn == "" {
|
||||
t.Fatal("headless integration requires isolated PostgreSQL")
|
||||
}
|
||||
db, err := sql.Open("pgx", dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
var entropy [12]byte
|
||||
_, _ = rand.Read(entropy[:])
|
||||
project := "headless-" + hex.EncodeToString(entropy[:])
|
||||
defer func() {
|
||||
for _, table := range []string{"canvas_model_operations", "canvas_model_identity_map", "canvas_model_requests", "canvas_model_projects", "canvas_asset_bindings", "canvas_subgraphs", "workspaces", "project_accounts"} {
|
||||
_, _ = db.ExecContext(ctx, "DELETE FROM "+table+" WHERE project=$1", project)
|
||||
}
|
||||
}()
|
||||
var account, workspace, root string
|
||||
if err = db.QueryRowContext(ctx, `INSERT INTO project_accounts(project,id) VALUES($1,gen_random_uuid()) RETURNING id::text`, project).Scan(&account); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var tokenBytes [32]byte
|
||||
_, _ = rand.Read(tokenBytes[:])
|
||||
token := base64.RawURLEncoding.EncodeToString(tokenBytes[:])
|
||||
hash := sha256.Sum256([]byte(token))
|
||||
if _, err = db.ExecContext(ctx, `INSERT INTO project_sessions(token_hash,project,account_id,expires_at) VALUES($1,$2,$3,now()+interval '1 hour')`, base64.RawURLEncoding.EncodeToString(hash[:]), project, account); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = db.QueryRowContext(ctx, `INSERT INTO workspaces(project,name,owner_id) VALUES($1,'Headless integration',$2) RETURNING id::text`, project, account).Scan(&workspace); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = db.ExecContext(ctx, `INSERT INTO workspace_members(project,workspace_id,account_id,role) VALUES($1,$2,$3,'admin')`, project, workspace, account); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
legacy := json.RawMessage(`{"schema_version":1,"nodes":[` + modelLegacyFrame + `,{"id":"a","type":"plainText","position":{"x":150,"y":260},"width":240,"height":72,"data":{"kind":"plain-text","title":"a","text":"preserved","frameId":"f"}}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}}`)
|
||||
if err = db.QueryRowContext(ctx, `INSERT INTO materials(project,workspace_id,kind,title,created_by,canvas_document) VALUES($1,$2,'board','Root',$3,$4) RETURNING id::text`, project, workspace, account, legacy).Scan(&root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
Register(mux, db, map[string]string{project: "https://app.example.com/auth/callback"}, "https://api.example.com", nil)
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
commandContext, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
command := exec.CommandContext(commandContext, "node", filepath.Join(source, "tests/headless/foundry.integration.mjs"))
|
||||
command.Env = append(os.Environ(), "FOUNDRY_HTTP_URL="+server.URL, "FOUNDRY_TEST_PROJECT="+project, "FOUNDRY_TEST_ACCOUNT="+account, "FOUNDRY_TEST_WORKSPACE="+workspace, "FOUNDRY_TEST_ROOT="+root, "FOUNDRY_TEST_TOKEN="+token)
|
||||
output, err := command.CombinedOutput()
|
||||
// The fixture token is ephemeral, but never print it even on client failures.
|
||||
report := strings.ReplaceAll(string(output), token, "[fixture token redacted]")
|
||||
if err != nil {
|
||||
t.Fatalf("headless HTTP client failed: %v\n%s", err, report)
|
||||
}
|
||||
if !strings.Contains(report, `"status":"ok"`) {
|
||||
t.Fatalf("headless HTTP client did not return success: %s", report)
|
||||
}
|
||||
t.Log(report)
|
||||
}
|
||||
Reference in New Issue
Block a user