Compare commits

..
Author SHA1 Message Date
dependabot[bot] c06c29b716 chore(deps): bump the actions group across 1 directory with 13 updates
Bumps the actions group with 13 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-python](https://github.com/actions/setup-python) | `7.0.0` | `7.1.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `7.0.0` | `7.1.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `10.2.0` | `10.3.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.7` | `4.39.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.7` | `4.39.1` |
| [hadolint/hadolint-action](https://github.com/hadolint/hadolint-action) | `3.4.0` | `3.5.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.3.0` | `4.4.1` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.3.0` | `7.4.0` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.37.7` | `4.39.1` |
| [actions/deploy-pages](https://github.com/actions/deploy-pages) | `5.0.0` | `5.0.1` |
| [docker/login-action](https://github.com/docker/login-action) | `4.6.0` | `4.7.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |



Updates `actions/setup-python` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/5fda3b95a4ea91299a34e894583c3862153e4b97...6d4fd22b20465ccc0f7354476588e8df7c9ca0e0)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `astral-sh/setup-uv` from 10.2.0 to 10.3.2
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/c18668ad3cf93ea998bef934396af7bb5c839dc7...07a047e608db84b3d8c06cd9a7c7dcc9c355c18e)

Updates `github/codeql-action/init` from 4.37.7 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

Updates `github/codeql-action/analyze` from 4.37.7 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

Updates `hadolint/hadolint-action` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/hadolint/hadolint-action/releases)
- [Commits](https://github.com/hadolint/hadolint-action/compare/2a66e89f53d0771bb131a7fa31f3136336094aa6...06be81baf89a55ffd0e24b8f04a4185738dd3387)

Updates `docker/setup-buildx-action` from 4.3.0 to 4.4.1
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069)

Updates `docker/build-push-action` from 7.3.0 to 7.4.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

Updates `github/codeql-action/upload-sarif` from 4.37.7 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

Updates `actions/deploy-pages` from 5.0.0 to 5.0.1
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/cd2ce8fcbc39b97be8ca5fce6e763baed58fa128...368f82528645a54fb793d4d04e342629a3f51346)

Updates `docker/login-action` from 4.6.0 to 4.7.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/dbcb813823bdd20940b903addbd779551569679f...81c6f93d4bc991e510b342a00b419b3bb4efc5c6)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: hadolint/hadolint-action
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/build-push-action
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/deploy-pages
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: docker/login-action
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-29 17:41:48 +00:00
20 changed files with 32 additions and 458 deletions
-17
View File
@@ -1,17 +0,0 @@
"""Reject skipped/empty native CI runs without conflating driver child counts."""
import argparse
from pathlib import Path
import xml.etree.ElementTree as ET
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('report', type=Path)
parser.add_argument('--minimum', type=int, required=True)
args = parser.parse_args()
root = ET.parse(args.report).getroot()
cases = list(root.iter('testcase'))
if len(cases) < args.minimum:
raise SystemExit(f'Incomplete execution: {len(cases)} cases, expected at least {args.minimum}')
for case in cases:
if any(case.find(tag) is not None for tag in ('skipped', 'failure', 'error')):
raise SystemExit(f'Case did not pass: {case.get("classname")}::{case.get("name")}')
print(f'Verified {len(cases)} executed cases; no skipped, failed or errored cases')
-46
View File
@@ -1,46 +0,0 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import { parse } from 'acorn';
const root = fileURLToPath(new URL('../../', import.meta.url));
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'tests/native-ci-manifest.json'), 'utf8'));
function files(directory) {
return fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? files(target) : /\.(?:mjs|cjs|js)$/.test(entry.name) ? [target] : [];
});
}
function importsNativeTest(node) {
if (!node || typeof node !== 'object') return false;
if (node.type === 'ImportDeclaration' && node.source.value === 'node:test') return true;
if (node.type === 'CallExpression' && node.callee.name === 'require' && node.arguments[0]?.value === 'node:test') return true;
return Object.values(node).some(value => Array.isArray(value) ? value.some(importsNativeTest) : importsNativeTest(value));
}
const discovered = files(path.join(root, 'tests')).filter(file => {
const source = fs.readFileSync(file, 'utf8');
return source.includes('node:test') && importsNativeTest(parse(source, { ecmaVersion: 'latest', sourceType: 'module' }));
}).map(file => path.relative(root, file).split(path.sep).join('/')).sort();
const assigned = Object.values(manifest).flatMap(group => Array.isArray(group) ? group : Object.keys(group));
if (new Set(assigned).size !== assigned.length || JSON.stringify([...assigned].sort()) !== JSON.stringify(discovered)) {
throw new Error('Native test coverage must be exhaustive and disjoint; update tests/native-ci-manifest.json for added, removed or duplicated files');
}
for (const wrappers of Object.values(manifest['python-ci'])) {
for (const wrapper of wrappers) if (!fs.existsSync(path.join(root, wrapper))) throw new Error(`Missing pytest wrapper: ${wrapper}`);
}
if (process.argv.includes('--list')) {
console.log(JSON.stringify({ discovered, runners: manifest }, null, 2));
} else {
const reports = path.join(root, 'test-results/native-node');
fs.mkdirSync(reports, { recursive: true });
const result = spawnSync(process.execPath, ['--test', '--test-concurrency=1',
'--test-reporter=spec', '--test-reporter-destination=stdout',
'--test-reporter=junit', `--test-reporter-destination=${path.join(reports, 'junit.xml')}`,
...manifest['native-ci']], {
cwd: root, stdio: 'inherit',
env: { ...process.env, ENDPOINT_URL: 'http://127.0.0.1:1' },
});
if (result.error) throw result.error;
process.exitCode = result.status ?? 1;
}
+5 -5
View File
@@ -76,7 +76,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with: with:
python-version: "3.11" python-version: "3.11"
# Byte-compile sources — catches syntax errors without installing deps. # Byte-compile sources — catches syntax errors without installing deps.
@@ -91,7 +91,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with: with:
node-version: "22.23.4" node-version: "22.23.4"
- run: npm ci --ignore-scripts --no-audit --no-fund - run: npm ci --ignore-scripts --no-audit --no-fund
@@ -151,7 +151,7 @@ jobs:
echo "docs_only=false" >> "$GITHUB_OUTPUT" echo "docs_only=false" >> "$GITHUB_OUTPUT"
fi fi
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
if: steps.docs-check.outputs.docs_only != 'true' if: steps.docs-check.outputs.docs_only != 'true'
with: with:
python-version: "3.11" python-version: "3.11"
@@ -168,7 +168,7 @@ jobs:
# uv resolves the same versions as pip but installs ~5x faster, and its # uv resolves the same versions as pip but installs ~5x faster, and its
# cache keeps each shard from re-downloading the whole requirement set. # cache keeps each shard from re-downloading the whole requirement set.
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
if: steps.docs-check.outputs.docs_only != 'true' if: steps.docs-check.outputs.docs_only != 'true'
with: with:
version: "0.13.5" version: "0.13.5"
@@ -180,7 +180,7 @@ jobs:
python scripts/check_ca_trust.py --python python scripts/check_ca_trust.py --python
uv pip install --system -r requirements.txt uv pip install --system -r requirements.txt
if: steps.docs-check.outputs.docs_only != 'true' if: steps.docs-check.outputs.docs_only != 'true'
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
if: steps.docs-check.outputs.docs_only != 'true' if: steps.docs-check.outputs.docs_only != 'true'
with: with:
node-version: "22.23.4" node-version: "22.23.4"
+2 -2
View File
@@ -31,11 +31,11 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: none build-mode: none
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with: with:
category: "/language:${{ matrix.language }}" category: "/language:${{ matrix.language }}"
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Lint Dockerfile - name: Lint Dockerfile
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0
with: with:
dockerfile: Dockerfile dockerfile: Dockerfile
# DL3008: pinning apt package versions is impractical on a -slim base # DL3008: pinning apt package versions is impractical on a -slim base
+5 -5
View File
@@ -61,14 +61,14 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
with: with:
driver: docker driver: docker
# Build without pushing so a broken Dockerfile is caught here, and the # Build without pushing so a broken Dockerfile is caught here, and the
# exact image we ship is what gets scanned. # exact image we ship is what gets scanned.
- name: Build image - name: Build image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with: with:
context: . context: .
push: false push: false
@@ -107,12 +107,12 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
with: with:
driver: docker driver: docker
- name: Build image - name: Build image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with: with:
context: . context: .
push: false push: false
@@ -133,7 +133,7 @@ jobs:
TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2 TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2
- name: Upload Trivy results - name: Upload Trivy results
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with: with:
sarif_file: trivy-results.sarif sarif_file: trivy-results.sarif
category: trivy-image category: trivy-image
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Python - name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with: with:
python-version: '3.12' python-version: '3.12'
+1 -1
View File
@@ -47,4 +47,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
+7 -7
View File
@@ -55,16 +55,16 @@ jobs:
shell: bash shell: bash
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to GHCR - name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with: with:
context: . context: .
pull: true pull: true
@@ -78,7 +78,7 @@ jobs:
digest="${{ steps.build.outputs.digest }}" digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}" touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with: with:
name: digest-${{ matrix.arch }} name: digest-${{ matrix.arch }}
path: /tmp/digests/* path: /tmp/digests/*
@@ -107,15 +107,15 @@ jobs:
echo "version=$v" >> "$GITHUB_OUTPUT" echo "version=$v" >> "$GITHUB_OUTPUT"
echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Download digests - name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with: with:
path: /tmp/digests path: /tmp/digests
pattern: digest-* pattern: digest-*
merge-multiple: true merge-multiple: true
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to GHCR - name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
-119
View File
@@ -1,119 +0,0 @@
name: Native test suites
on:
pull_request:
push:
branches: [main, dev]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: native-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
native-node:
name: Native Node regressions
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.23.4'
cache: npm
- run: npm ci --ignore-scripts --no-audit --no-fund
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: ms-playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx playwright install --with-deps chromium
- name: Validate exhaustive disjoint runner mapping and execute omitted suites
run: |
node .github/scripts/run_native_tests.mjs
python .github/scripts/check_test_execution.py test-results/native-node/junit.xml --minimum 43
- name: Save native test identities and results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-node-results
path: test-results/native-node/
retention-days: 7
if-no-files-found: warn
browser:
name: Chromium application regressions
runs-on: ubuntu-24.04
timeout-minutes: 30
env:
ODYSSEUS_E2E_DISPOSABLE_ARTIFACTS: '1'
ODYSSEUS_TEST_PYTHON: python
CHROMADB_HOST: 127.0.0.1
CHROMADB_PORT: '1'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install FFmpeg and establish system trust
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ffmpeg ca-certificates
python scripts/check_ca_trust.py --system
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
version: '0.13.5'
enable-cache: true
cache-dependency-glob: requirements.txt
prune-cache: false
- run: |
uv pip install --system --require-hashes --only-binary=:all: --no-deps -r requirements-bootstrap.txt
python scripts/check_ca_trust.py --python
uv pip install --system -r requirements.txt
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.23.4'
cache: npm
- run: npm ci --ignore-scripts --no-audit --no-fund
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: ms-playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx playwright install --with-deps chromium
- name: Run complete Chromium suite against a fresh authenticated app
env:
PLAYWRIGHT_JUNIT_OUTPUT_FILE: test-results/browser-junit.xml
run: |
npm run test:photo-editor -- --reporter=line,junit
python .github/scripts/check_test_execution.py test-results/browser-junit.xml --minimum 130
- name: Save per-test results and sanitized failure diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: chromium-regression-results
path: test-results/
retention-days: 7
if-no-files-found: warn
authoritative-native-suites:
name: Authoritative native suites
if: always()
needs: [native-node, browser]
runs-on: ubuntu-24.04
steps:
- name: Require both complete suites to succeed
env:
NODE_RESULT: ${{ needs.native-node.result }}
BROWSER_RESULT: ${{ needs.browser.result }}
run: |
test "$NODE_RESULT" = success
test "$BROWSER_RESULT" = success
+2 -2
View File
@@ -62,9 +62,9 @@ jobs:
env: env:
SEARXNG_TEST_IMAGE: ${{ steps.image.outputs.image }} SEARXNG_TEST_IMAGE: ${{ steps.image.outputs.image }}
run: docker pull "$SEARXNG_TEST_IMAGE" run: docker pull "$SEARXNG_TEST_IMAGE"
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Check unmodified upstream trust and actual entrypoint offline - name: Check unmodified upstream trust and actual entrypoint offline
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with: with:
context: . context: .
file: docker/searxng/Dockerfile file: docker/searxng/Dockerfile
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Python - name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with: with:
python-version: '3.12' python-version: '3.12'
-38
View File
@@ -1,38 +0,0 @@
# Native test merge gate
`Native test suites` runs on pull requests, dev/main pushes and manual dispatch.
`Authoritative native suites` fails unless both the native Node and complete
Chromium jobs succeed; it has no continue-on-error, retries or path bypass.
Repository administrators must require this check alongside the four pytest
checks in branch protection. The audited public dev branch had no protection or
ruleset, so merely adding workflow jobs cannot prevent a manual merge.
`tests/native-ci-manifest.json` exhaustively partitions all native Node files:
16 run in the native job,14 through existing pytest wrappers, one through the
existing frontend architecture CI step, and the formerly live email-reply file
through an explicitly identified Playwright driver. The manifest validator fails
for omitted/duplicate/unreachable entries. Node emits individual JUnit identities
and propagates its failure exit. No native file is newly run in each pytest shard.
The browser job uses the existing one-worker Chromium config and all original129
cases, plus one driver for the native email-reply contract. Its ephemeral account,
SQLite DB and server are created by the config/setup; reuseExistingServer remains
false. NPX startup is offline/cache-only in a private cache; Notes/editor behavior
and Python MCP services remain real. No developer app or external account is used.
Default CI trace suppression remains intact. Only this job's explicit opt-in plus
a newly generated authenticated synthetic fixture marker permits failure traces.
The existing structured sanitizer runs before artifacts are uploaded; invalid
ownership, failed sanitization and missing tooling purge sensitive artifacts.
Never upload the fixture directory, database or browser storage state.
The stroke budget now excludes measured synthetic event-delivery pacing and also
bounds actual Chromium main-thread work at the original5s. All240 samples and
pixel/thumbnail/undo/redo assertions remain. Per-case timing attachments expose
both clocks rather than treating driver frame cadence as application work.
Three Bombadil properties remain opt-in: they require a Bombadil/browser scenario
and bounded fuzz campaign; no authoritative execution is claimed. The two model
smoke scripts require supplied model endpoints/models and remain explicit live
operations. No credentials or live-service authorization are implied by CI setup.
Firefox/WebKit alternatives remain opt-in and are not certified by Chromium CI.
-19
View File
@@ -1,19 +0,0 @@
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const marker = '.disposable-ci-fixture.json';
function disposableCIArtifacts() {
if (!(process.env.CI || process.env.GITHUB_ACTIONS) || process.env.ODYSSEUS_E2E_DISPOSABLE_ARTIFACTS !== '1') return false;
const directory = process.env.PHOTO_EDITOR_E2E_DATA_DIR;
if (!directory || process.env.PHOTO_EDITOR_E2E_AUTH === '0') return false;
const resolved = path.resolve(directory);
if (!path.basename(resolved).startsWith('odysseus-photo-editor-e2e-')) return false;
if (![os.tmpdir(), '/tmp', '/var/tmp'].some(base => resolved.startsWith(path.resolve(base) + path.sep))) return false;
if (process.env.PHOTO_EDITOR_E2E_DB_PATH !== path.join(resolved, 'app.db')) return false;
try {
const fixture = JSON.parse(fs.readFileSync(path.join(resolved, marker), 'utf8'));
return fixture.synthetic === true && fixture.authenticated === true && fixture.database === path.join(resolved, 'app.db');
} catch { return false; }
}
module.exports = { disposableCIArtifacts, marker };
+2 -29
View File
@@ -35,7 +35,7 @@ test('brush presets and shared stroke controls remain reusable', async ({ page }
await expect(page.locator('#ge-brush-preset option', { hasText: 'My Detail Brush' })).toHaveCount(0); await expect(page.locator('#ge-brush-preset option', { hasText: 'My Detail Brush' })).toHaveCount(0);
}); });
test('long sampled strokes stay responsive and undo atomically', async ({ page, browserName }, testInfo) => { test('long sampled strokes stay responsive and undo atomically', async ({ page }) => {
await openBlankEditor(page); await openBlankEditor(page);
await selectTool(page, 'brush'); await selectTool(page, 'brush');
await page.locator('.ge-size-slider').fill('700'); await page.locator('.ge-size-slider').fill('700');
@@ -49,40 +49,13 @@ test('long sampled strokes stay responsive and undo atomically', async ({ page,
const before = await flattenedPixelDigest(page); const before = await flattenedPixelDigest(page);
const start = await canvasPoint(page, 0.08, 0.5); const start = await canvasPoint(page, 0.08, 0.5);
const end = await canvasPoint(page, 0.92, 0.5); const end = await canvasPoint(page, 0.92, 0.5);
// Playwright delivers stepped moves at frame cadence: 240 events already
// cost ~4s on an inert page. Measure that transport cost with the same input,
// and separately bound real Chromium main-thread work at the original5s.
const control = await page.context().newPage();
let controlDuration;
try {
await control.setContent('<canvas style="position:fixed;inset:0;width:100vw;height:100vh"></canvas>');
const started = Date.now();
await control.mouse.move(start.x, start.y);
await control.mouse.down();
await control.mouse.move(end.x, end.y, { steps: 240 });
await control.mouse.up();
controlDuration = Date.now() - started;
} finally {
await control.close();
await page.bringToFront();
}
const metrics = browserName === 'chromium' ? await page.context().newCDPSession(page) : null;
if (metrics) await metrics.send('Performance.enable');
const taskTime = async () => (await metrics.send('Performance.getMetrics')).metrics.find(item => item.name === 'TaskDuration').value *1000;
const beforeTask = metrics ? await taskTime() : null;
const startedAt = Date.now(); const startedAt = Date.now();
await page.mouse.move(start.x, start.y); await page.mouse.move(start.x, start.y);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(end.x, end.y, { steps: 240 }); await page.mouse.move(end.x, end.y, { steps: 240 });
await page.mouse.up(); await page.mouse.up();
const strokeDuration = Date.now() - startedAt; const strokeDuration = Date.now() - startedAt;
const mainThreadDuration = metrics ? (await taskTime()) - beforeTask : null; expect(strokeDuration).toBeLessThan(5000);
if (metrics) await metrics.detach();
await testInfo.attach('stroke-timing', { body: Buffer.from(JSON.stringify({
samples: 240, strokeDuration, controlDuration, mainThreadDuration,
})), contentType: 'application/json' });
expect(strokeDuration - controlDuration).toBeLessThan(5000);
if (mainThreadDuration !== null) expect(mainThreadDuration).toBeLessThan(5000);
const after = await flattenedPixelDigest(page); const after = await flattenedPixelDigest(page);
expect(after).not.toEqual(before); expect(after).not.toEqual(before);
await expect(thumb).toHaveAttribute('width', '68'); await expect(thumb).toHaveAttribute('width', '68');
@@ -1,21 +0,0 @@
const { test, expect } = require('@playwright/test');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
// The previously opt-in node:test now uses only this config's owned app/account.
// Its native JUnit report retains its authored identity; this case is a driver.
test('native email reply regression on the disposable authenticated app', async ({ context, baseURL }, testInfo) => {
const origin = new URL(baseURL);
expect(origin.hostname).toBe('127.0.0.1');
const cookie = (await context.cookies(baseURL)).find(item => item.name === 'odysseus_session');
expect(cookie?.value).toBeTruthy();
const report = testInfo.outputPath('native-email-junit.xml');
const result = spawnSync(process.execPath, ['--test', '--test-reporter=spec', '--test-reporter-destination=stdout',
'--test-reporter=junit', `--test-reporter-destination=${report}`, 'tests/emailReplyBrowser.test.mjs'], {
cwd: path.resolve(__dirname, '../../..'), encoding: 'utf8', timeout: 80_000,
env: { ...process.env, ODYSSEUS_UI_TEST_URL: baseURL, ODYSSEUS_UI_TEST_COOKIE: cookie.value },
});
console.log(result.stdout);
expect(result.error, String(result.error)).toBeUndefined();
expect(result.status, result.stdout + result.stderr).toBe(0);
});
+3 -17
View File
@@ -2,12 +2,6 @@ const fs = require('node:fs');
const os = require('node:os'); const os = require('node:os');
const path = require('node:path'); const path = require('node:path');
const { defineConfig } = require('@playwright/test'); const { defineConfig } = require('@playwright/test');
const { disposableCIArtifacts, marker } = require('./artifact-policy');
const isCI = Boolean(process.env.CI) || Boolean(process.env.GITHUB_ACTIONS);
const disposableOptIn = isCI && process.env.ODYSSEUS_E2E_DISPOSABLE_ARTIFACTS === '1';
if (disposableOptIn && (process.env.PHOTO_EDITOR_E2E_DATA_DIR || process.env.PHOTO_EDITOR_E2E_DB_PATH || process.env.PHOTO_EDITOR_E2E_AUTH === '0') && !disposableCIArtifacts()) {
throw new Error('CI artifact opt-in requires fresh authenticated fixture paths');
}
const port = Number(process.env.PHOTO_EDITOR_E2E_PORT || 7013); const port = Number(process.env.PHOTO_EDITOR_E2E_PORT || 7013);
const baseURL = `http://127.0.0.1:${port}`; const baseURL = `http://127.0.0.1:${port}`;
@@ -30,9 +24,7 @@ if (!['chromium', 'firefox', 'webkit'].includes(browserName)) {
throw new Error(`Unsupported PHOTO_EDITOR_E2E_BROWSER: ${browserName}`); throw new Error(`Unsupported PHOTO_EDITOR_E2E_BROWSER: ${browserName}`);
} }
if (disposableOptIn) { const isCI = Boolean(process.env.CI) || Boolean(process.env.GITHUB_ACTIONS);
fs.writeFileSync(path.join(dataDirectory, marker), JSON.stringify({ synthetic: true, authenticated: true, database: databasePath }));
}
const traceOptIn = !isCI && ['1', 'true'].includes(String(process.env.PLAYWRIGHT_ENABLE_TRACE || '').toLowerCase()); const traceOptIn = !isCI && ['1', 'true'].includes(String(process.env.PLAYWRIGHT_ENABLE_TRACE || '').toLowerCase());
module.exports = defineConfig({ module.exports = defineConfig({
@@ -40,8 +32,6 @@ module.exports = defineConfig({
outputDir: path.join(__dirname, '..', '..', 'test-results', 'photo-editor'), outputDir: path.join(__dirname, '..', '..', 'test-results', 'photo-editor'),
timeout: 90_000, timeout: 90_000,
expect: { timeout: 10_000 }, expect: { timeout: 10_000 },
forbidOnly: isCI,
retries: 0,
fullyParallel: false, fullyParallel: false,
workers: 1, workers: 1,
reporter: isCI ? 'line' : 'line', reporter: isCI ? 'line' : 'line',
@@ -59,9 +49,9 @@ module.exports = defineConfig({
serviceWorkers: 'block', serviceWorkers: 'block',
...(authenticated ? { storageState: path.join(dataDirectory, 'browser-auth.json') } : {}), ...(authenticated ? { storageState: path.join(dataDirectory, 'browser-auth.json') } : {}),
screenshot: 'only-on-failure', screenshot: 'only-on-failure',
// Default CI suppression remains; only the fresh synthetic fixture opts in. // Unconditional CI trace suppression: Playwright traces are NEVER recorded in CI.
// Local developer tracing requires explicit PLAYWRIGHT_ENABLE_TRACE=1. // Local developer tracing requires explicit PLAYWRIGHT_ENABLE_TRACE=1.
trace: (traceOptIn || disposableCIArtifacts()) ? 'retain-on-failure' : 'off', trace: traceOptIn ? 'retain-on-failure' : 'off',
}, },
webServer: { webServer: {
command: `${JSON.stringify(python)} -m uvicorn app:app --host 127.0.0.1 --port ${port}`, command: `${JSON.stringify(python)} -m uvicorn app:app --host 127.0.0.1 --port ${port}`,
@@ -75,10 +65,6 @@ module.exports = defineConfig({
DATABASE_URL: `sqlite:///${databasePath}`, DATABASE_URL: `sqlite:///${databasePath}`,
ODYSSEUS_DATA_DIR: dataDirectory, ODYSSEUS_DATA_DIR: dataDirectory,
ODYSSEUS_STARTUP_WARMUPS: '0', ODYSSEUS_STARTUP_WARMUPS: '0',
ODYSSEUS_TOOL_INDEX_PREWARM: '0',
ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE: '1',
npm_config_offline: 'true',
npm_config_cache: path.join(dataDirectory, 'npm-cache'),
RESPONSE_COMPRESSION_ENABLED: 'false', RESPONSE_COMPRESSION_ENABLED: 'false',
}, },
}, },
+2 -7
View File
@@ -2,7 +2,6 @@ const fs = require('node:fs');
const os = require('node:os'); const os = require('node:os');
const path = require('node:path'); const path = require('node:path');
const { execFileSync } = require('node:child_process'); const { execFileSync } = require('node:child_process');
const { disposableCIArtifacts } = require('./artifact-policy');
const repoRoot = path.resolve(__dirname, '..', '..'); const repoRoot = path.resolve(__dirname, '..', '..');
const defaultResultsDir = path.join(repoRoot, 'test-results'); const defaultResultsDir = path.join(repoRoot, 'test-results');
@@ -30,9 +29,6 @@ module.exports = async (options = {}) => {
const isPlaywrightConfig = options && (options.configFile || options.rootDir); const isPlaywrightConfig = options && (options.configFile || options.rootDir);
const testOpts = (!isPlaywrightConfig && typeof options === 'object') ? options : {}; const testOpts = (!isPlaywrightConfig && typeof options === 'object') ? options : {};
// Validate fixture ownership before removing its temporary directory.
const disposableCI = disposableCIArtifacts();
// Confine cleanup of PHOTO_EDITOR_E2E_DATA_DIR: // Confine cleanup of PHOTO_EDITOR_E2E_DATA_DIR:
// Strictly require it to be within an OS temporary directory and match the expected test prefix // Strictly require it to be within an OS temporary directory and match the expected test prefix
const dataDirectory = process.env.PHOTO_EDITOR_E2E_DATA_DIR; const dataDirectory = process.env.PHOTO_EDITOR_E2E_DATA_DIR;
@@ -104,7 +100,7 @@ module.exports = async (options = {}) => {
const isCI = Boolean(process.env.CI) || Boolean(process.env.GITHUB_ACTIONS); const isCI = Boolean(process.env.CI) || Boolean(process.env.GITHUB_ACTIONS);
const traceOptIn = !isCI && ['1', 'true'].includes(String(process.env.PLAYWRIGHT_ENABLE_TRACE || '').toLowerCase()); const traceOptIn = !isCI && ['1', 'true'].includes(String(process.env.PLAYWRIGHT_ENABLE_TRACE || '').toLowerCase());
if ((isCI && !disposableCI) || (!traceOptIn && !disposableCI)) { if (isCI || !traceOptIn) {
// In CI or default local runs, purge all sensitive trace archives and artifacts // In CI or default local runs, purge all sensitive trace archives and artifacts
purgeSensitiveArtifacts(resultsDir); purgeSensitiveArtifacts(resultsDir);
} else { } else {
@@ -128,8 +124,7 @@ module.exports = async (options = {}) => {
if (!sanitizerSucceeded) { if (!sanitizerSucceeded) {
// Fail closed: purge all remaining zip or tmp archives // Fail closed: purge all remaining zip or tmp archives
if (disposableCI) purgeSensitiveArtifacts(resultsDir); purgeZipAndTmp(resultsDir);
else purgeZipAndTmp(resultsDir);
} }
} }
} }
-71
View File
@@ -1,71 +0,0 @@
{
"native-ci": [
"tests/backgroundToolJobs.test.mjs",
"tests/chatEditorProgress.test.mjs",
"tests/chatImageDeletion.test.mjs",
"tests/chatPhotoEditorIntegrity.test.mjs",
"tests/chatProcessingHandoff.test.mjs",
"tests/copyChatHeader.test.mjs",
"tests/emailReplyStream.test.mjs",
"tests/galleryModelPickerSync.test.mjs",
"tests/generatedImageResult.test.mjs",
"tests/researchMobileButtons.test.mjs",
"tests/schemaThinkingProbe.test.mjs",
"tests/sidebarNewChat.test.mjs",
"tests/skillsApproval.test.mjs",
"tests/streamingRendererRaf.test.mjs",
"tests/toolFollowupOracle.test.mjs",
"tests/tool_followup_oracle.test.mjs"
],
"browser-ci": [
"tests/emailReplyBrowser.test.mjs"
],
"python-ci": {
"tests/historyResumeRendering.test.mjs": [
"tests/test_history_resume_rendering_js.py",
"tests/test_live_fallback_round_attribution.py"
],
"tests/live_thinking_scheduler.test.mjs": [
"tests/test_live_thinking_scheduler_js.py"
],
"tests/noteTestOracle.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesApplication.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesCharacterization.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesDraftAutosave.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesInfrastructure.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesRecurrence.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesReminders.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/notesWorkflows.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/selectedNotesContext.test.mjs": [
"tests/test_notes_reference_contracts_js.py"
],
"tests/streaming/invariant.test.mjs": [
"tests/test_streaming_segmenter_js.py"
],
"tests/streaming/segmenter.test.mjs": [
"tests/test_streaming_segmenter_js.py"
],
"tests/turnRendering.test.mjs": [
"tests/test_turn_rendering_js.py"
]
},
"architecture-ci": [
"tests/frontendArchitecture.test.mjs"
]
}
@@ -303,52 +303,3 @@ def test_teardown_rejects_unconfined_programmatic_paths():
res = _run_teardown(options={"resultsDir": "/etc"}) res = _run_teardown(options={"resultsDir": "/etc"})
assert res.returncode == 1 assert res.returncode == 1
assert "Security rejection" in res.stderr assert "Security rejection" in res.stderr
def _disposable_ci_fixture(tmp_path):
data = tmp_path / "odysseus-photo-editor-e2e-fixture"
data.mkdir()
database = data / "app.db"
(data / ".disposable-ci-fixture.json").write_text(json.dumps({
"synthetic": True, "authenticated": True, "database": str(database),
}))
return {"CI": "true", "GITHUB_ACTIONS": "true",
"ODYSSEUS_E2E_DISPOSABLE_ARTIFACTS": "1",
"PHOTO_EDITOR_E2E_DATA_DIR": str(data), "PHOTO_EDITOR_E2E_DB_PATH": str(database),
"PHOTO_EDITOR_E2E_AUTH": "1", "ODYSSEUS_TEST_PYTHON": sys.executable}
def test_disposable_ci_retains_only_sanitized_failure_artifacts(tmp_path):
results = tmp_path / "results"
results.mkdir()
trace = results / "trace.zip"
with zipfile.ZipFile(trace, "w") as archive:
archive.writestr("0-trace.trace", json.dumps({"type": "action", "name": "fill", "params": {"value": "synthetic-secret"}}) + "\n")
screenshot = results / "failure.png"
screenshot.write_bytes(b"synthetic screenshot")
response = _run_teardown(_disposable_ci_fixture(tmp_path), {"resultsDir": str(results)})
assert response.returncode == 0, response.stderr
with zipfile.ZipFile(trace) as archive:
assert json.loads(archive.read("0-trace.trace"))["params"]["value"] == "[REDACTED]"
assert screenshot.exists()
@pytest.mark.parametrize("defect", ["missing-marker", "wrong-database", "unauthenticated", "sanitizer-failure"])
def test_disposable_ci_artifact_failures_purge_closed(tmp_path, defect):
results = tmp_path / "results"
results.mkdir()
(results / "trace.zip").write_bytes(b"invalid sensitive archive")
(results / "failure.png").write_bytes(b"sensitive screenshot")
environment = _disposable_ci_fixture(tmp_path)
if defect == "missing-marker":
(Path(environment["PHOTO_EDITOR_E2E_DATA_DIR"]) / ".disposable-ci-fixture.json").unlink()
elif defect == "wrong-database":
environment["PHOTO_EDITOR_E2E_DB_PATH"] = str(tmp_path / "other.db")
elif defect == "unauthenticated":
environment["PHOTO_EDITOR_E2E_AUTH"] = "0"
else:
environment["ODYSSEUS_TEST_PYTHON"] = "/missing-python"
response = _run_teardown(environment, {"resultsDir": str(results)})
assert response.returncode == 0, response.stderr
assert not (results / "trace.zip").exists()
assert not (results / "failure.png").exists()