Skip to content

feat(ai): migrate AI features from BYOK to XTM One agents (#149) #386

feat(ai): migrate AI features from BYOK to XTM One agents (#149)

feat(ai): migrate AI features from BYOK to XTM One agents (#149) #386

Workflow file for this run

name: CI
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
workflow_dispatch:
jobs:
# Lint and type check - run first and fast
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run TypeScript type check
run: npx tsc --noEmit
# Unit tests - run after lint passes
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests with coverage
run: npm run test:unit -- --coverage
- name: Upload unit test coverage
uses: actions/upload-artifact@v7
with:
name: coverage-unit
path: coverage/coverage-final.json
retention-days: 1
# OpenCTI integration tests
integration-opencti:
name: OpenCTI Integration Tests
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 30
services:
redis:
image: redis:8.6.1
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.16
env:
discovery.type: single-node
xpack.ml.enabled: 'false'
xpack.security.enabled: 'false'
thread_pool.search.queue_size: '5000'
ES_JAVA_OPTS: -Xms2g -Xmx2g
ports:
- 9200:9200
options: >-
--health-cmd "curl -sf http://localhost:9200 || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 30
minio:
image: lazybit/minio
ports:
- 9000:9000
- 9001:9001
env:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
options: >-
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 10
rabbitmq:
image: rabbitmq:4.2-management
ports:
- 5672:5672
- 15672:15672
env:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
options: >-
--health-cmd "rabbitmq-diagnostics -q ping"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Wait for Elasticsearch to be ready
run: |
echo "Waiting for Elasticsearch..."
for i in {1..60}; do
if curl -sf http://localhost:9200 >/dev/null 2>&1; then
echo "Elasticsearch is ready!"
break
fi
echo "Attempt $i: Elasticsearch not ready yet..."
sleep 5
done
- name: Wait for MinIO to be ready
run: |
echo "Waiting for MinIO..."
for i in {1..30}; do
if curl -sf http://localhost:9000/minio/health/live >/dev/null 2>&1; then
echo "MinIO is ready!"
break
fi
echo "Attempt $i: MinIO not ready yet..."
sleep 2
done
- name: Pull and run OpenCTI
run: |
docker run -d --name opencti \
--network host \
-e NODE_OPTIONS=--max-old-space-size=8096 \
-e APP__PORT=8080 \
-e APP__BASE_URL=http://localhost:8080 \
-e APP__ADMIN__EMAIL=admin@opencti.io \
-e APP__ADMIN__PASSWORD=admin \
-e APP__ADMIN__TOKEN=bfa014e0-e02e-4aa6-a42b-603b19dcf159 \
-e APP__ENCRYPTION_KEY=wbQnxC2ZIoohYzkXW19LiYU2V3lfoCVFkBadFd/dFAY= \
-e APP__HEALTH_ACCESS_KEY=healthcheck \
-e REDIS__HOSTNAME=localhost \
-e REDIS__PORT=6379 \
-e ELASTICSEARCH__URL=http://localhost:9200 \
-e ELASTICSEARCH__NUMBER_OF_REPLICAS=0 \
-e MINIO__ENDPOINT=localhost \
-e MINIO__PORT=9000 \
-e MINIO__USE_SSL=false \
-e MINIO__ACCESS_KEY=minioadmin \
-e MINIO__SECRET_KEY=minioadmin \
-e RABBITMQ__HOSTNAME=localhost \
-e RABBITMQ__PORT=5672 \
-e RABBITMQ__PORT_MANAGEMENT=15672 \
-e RABBITMQ__MANAGEMENT_SSL=false \
-e RABBITMQ__USERNAME=guest \
-e RABBITMQ__PASSWORD=guest \
-e SMTP__HOSTNAME=localhost \
-e SMTP__PORT=25 \
-e PROVIDERS__LOCAL__STRATEGY=LocalStrategy \
opencti/platform:latest
- name: Wait for OpenCTI to be ready
run: |
echo "Waiting for OpenCTI to be ready..."
for i in {1..120}; do
if curl -s -H "Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159" http://localhost:8080/graphql -d '{"query":"{ about { version } }"}' -H "Content-Type: application/json" | grep -q "version"; then
echo "OpenCTI is ready!"
curl -s -H "Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159" http://localhost:8080/graphql -d '{"query":"{ about { version } }"}' -H "Content-Type: application/json"
break
fi
echo "Attempt $i: OpenCTI not ready yet..."
docker logs opencti --tail 20 2>/dev/null || true
sleep 10
done
- name: Seed test data in OpenCTI
run: |
echo "Creating test data in OpenCTI..."
API_URL="http://localhost:8080/graphql"
AUTH_HEADER="Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159"
# Create Threat Actor Groups (actual organizations)
echo "Creating Threat Actor Group GRU..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { threatActorGroupAdd(input: { name: \"GRU\", aliases: [\"Main Intelligence Directorate\", \"Unit 26165\", \"Unit 74455\"], description: \"Russian military intelligence agency\" }) { id name } }"}' || true
echo "Creating Threat Actor Group Lazarus Group..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { threatActorGroupAdd(input: { name: \"Lazarus Group\", aliases: [\"HIDDEN COBRA\", \"Guardians of Peace\"], description: \"North Korean state-sponsored threat actor\" }) { id name } }"}' || true
# Create Intrusion Sets (adversary campaigns/operations)
echo "Creating Intrusion Set APT29..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { intrusionSetAdd(input: { name: \"APT29\", aliases: [\"Cozy Bear\", \"The Dukes\", \"IRON HEMLOCK\"], description: \"Russian cyber espionage group attributed to SVR\" }) { id name } }"}' || true
echo "Creating Intrusion Set APT28..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { intrusionSetAdd(input: { name: \"APT28\", aliases: [\"Fancy Bear\", \"Sofacy\", \"IRON TWILIGHT\"], description: \"Russian cyber espionage group attributed to GRU\" }) { id name } }"}' || true
# Create Malware
echo "Creating Malware Emotet..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { malwareAdd(input: { name: \"Emotet\", aliases: [\"Heodo\", \"Geodo\"], description: \"Banking trojan turned malware distribution service\", malware_types: [\"trojan\"], is_family: true }) { id name } }"}' || true
echo "Creating Malware Ryuk..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { malwareAdd(input: { name: \"Ryuk\", aliases: [\"WIZARD SPIDER\"], description: \"Ransomware targeting enterprises\", malware_types: [\"ransomware\"], is_family: true }) { id name } }"}' || true
# Create Attack Patterns (MITRE ATT&CK)
echo "Creating Attack Pattern T1566 Phishing..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { attackPatternAdd(input: { name: \"Phishing\", x_mitre_id: \"T1566\", description: \"Adversaries may send phishing messages to gain access to victim systems\" }) { id name } }"}' || true
echo "Creating Attack Pattern T1059 Command and Scripting Interpreter..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { attackPatternAdd(input: { name: \"Command and Scripting Interpreter\", x_mitre_id: \"T1059\", description: \"Adversaries may abuse command and script interpreters to execute commands\" }) { id name } }"}' || true
# Create an Indicator
echo "Creating test Indicator..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { indicatorAdd(input: { name: \"Malicious Domain\", pattern: \"[domain-name:value = '\''evil-domain.com'\'']\", pattern_type: \"stix\", valid_from: \"2024-01-01T00:00:00.000Z\" }) { id name } }"}' || true
# Create a Campaign
echo "Creating Campaign SolarWinds..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { campaignAdd(input: { name: \"SolarWinds Campaign\", aliases: [\"SUNBURST\", \"Solorigate\"], description: \"Supply chain attack discovered in December 2020\" }) { id name } }"}' || true
# Create Vulnerabilities (CVEs)
echo "Creating Vulnerability CVE-2021-44228..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { vulnerabilityAdd(input: { name: \"CVE-2021-44228\", description: \"Log4Shell - Remote code execution in Apache Log4j\" }) { id name } }"}' || true
echo "Creating Vulnerability CVE-2023-23397..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { vulnerabilityAdd(input: { name: \"CVE-2023-23397\", description: \"Microsoft Outlook privilege escalation vulnerability\" }) { id name } }"}' || true
# Create Tools
echo "Creating Tool Cobalt Strike..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { toolAdd(input: { name: \"Cobalt Strike\", aliases: [\"CS\", \"Beacon\"], description: \"Commercial adversary simulation and red team operations tool\" }) { id name } }"}' || true
echo "Creating Tool Mimikatz..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { toolAdd(input: { name: \"Mimikatz\", description: \"Credential dumping tool for Windows\" }) { id name } }"}' || true
# Create Labels
echo "Creating Labels..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { labelAdd(input: { value: \"apt\", color: \"#ff0000\" }) { id value } }"}' || true
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { labelAdd(input: { value: \"ransomware\", color: \"#ff6600\" }) { id value } }"}' || true
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"mutation { labelAdd(input: { value: \"state-sponsored\", color: \"#9900ff\" }) { id value } }"}' || true
echo "Test data seeding completed!"
- name: Wait for Elasticsearch indexing
run: |
echo "Waiting for Elasticsearch to index seeded data..."
sleep 15
- name: Verify test data
run: |
echo "Verifying test data..."
API_URL="http://localhost:8080/graphql"
AUTH_HEADER="Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159"
echo "Querying Threat Actor Groups..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"{ threatActorsGroup(first: 5) { edges { node { id name aliases } } } }"}'
echo ""
echo "Querying Intrusion Sets..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"{ intrusionSets(first: 5) { edges { node { id name aliases } } } }"}'
echo ""
echo "Querying Malware..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"{ malwares(first: 5) { edges { node { id name aliases } } } }"}'
echo ""
echo "Querying Attack Patterns..."
curl -s -X POST "$API_URL" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"query":"{ attackPatterns(first: 5) { edges { node { id name x_mitre_id } } } }"}'
- name: Run OpenCTI integration tests with coverage
env:
OPENCTI_URL: http://localhost:8080
OPENCTI_TOKEN: bfa014e0-e02e-4aa6-a42b-603b19dcf159
run: npm run test:opencti -- --coverage
- name: Upload OpenCTI integration test coverage
uses: actions/upload-artifact@v7
if: always()
with:
name: coverage-opencti
path: coverage/coverage-final.json
retention-days: 1
- name: Cleanup
if: always()
run: |
docker stop opencti || true
docker rm opencti || true
# OpenAEV integration tests
integration-openaev:
name: OpenAEV Integration Tests
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 30
services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_DB: openaev
POSTGRES_USER: openaev
POSTGRES_PASSWORD: openaev
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U openaev -d openaev"
--health-interval 10s
--health-timeout 5s
--health-retries 5
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.16
env:
discovery.type: single-node
xpack.ml.enabled: 'false'
xpack.security.enabled: 'false'
thread_pool.search.queue_size: '5000'
ES_JAVA_OPTS: -Xms2g -Xmx2g
ports:
- 9200:9200
options: >-
--health-cmd "curl -sf http://localhost:9200 || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 30
minio:
image: lazybit/minio
ports:
- 9000:9000
- 9001:9001
env:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
options: >-
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 10
rabbitmq:
image: rabbitmq:4.2-management
ports:
- 5672:5672
- 15672:15672
env:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
options: >-
--health-cmd "rabbitmq-diagnostics -q ping"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Wait for Elasticsearch to be ready
run: |
echo "Waiting for Elasticsearch..."
for i in {1..60}; do
if curl -sf http://localhost:9200 >/dev/null 2>&1; then
echo "Elasticsearch is ready!"
break
fi
echo "Attempt $i: Elasticsearch not ready yet..."
sleep 5
done
- name: Wait for MinIO to be ready
run: |
echo "Waiting for MinIO..."
for i in {1..30}; do
if curl -sf http://localhost:9000/minio/health/live >/dev/null 2>&1; then
echo "MinIO is ready!"
break
fi
echo "Attempt $i: MinIO not ready yet..."
sleep 2
done
- name: Pull and run OpenAEV
run: |
docker run -d --name openaev \
--network host \
-e OPENAEV_BASE-URL=http://localhost:8080 \
-e OPENAEV_AUTH-LOCAL-ENABLE=true \
-e OPENAEV_ADMIN_EMAIL=admin@openaev.io \
-e OPENAEV_ADMIN_PASSWORD=admin \
-e OPENAEV_ADMIN_TOKEN=bfa014e0-e02e-4aa6-a42b-603b19dcf159 \
-e OPENAEV_ADMIN_ENCRYPTION_KEY=ChangeMe \
-e OPENAEV_ADMIN_ENCRYPTION_SALT=ChangeMeSalt \
-e OPENAEV_HEALTHCHECK_KEY=healthcheck \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/openaev \
-e SPRING_DATASOURCE_USERNAME=openaev \
-e SPRING_DATASOURCE_PASSWORD=openaev \
-e MINIO_ENDPOINT=localhost \
-e MINIO_ACCESS-KEY=minioadmin \
-e MINIO_ACCESS-SECRET=minioadmin \
-e OPENAEV_RABBITMQ_HOSTNAME=localhost \
-e OPENAEV_RABBITMQ_USER=guest \
-e OPENAEV_RABBITMQ_PASS=guest \
-e ENGINE_URL=http://localhost:9200 \
openaev/platform:latest || echo "OpenAEV image may not be available, running mock tests only"
- name: Wait for OpenAEV to be ready
run: |
echo "Waiting for OpenAEV to be ready..."
for i in {1..90}; do
if curl -s -H "Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159" http://localhost:8080/api/settings 2>/dev/null | grep -q "platform"; then
echo "OpenAEV is ready!"
curl -s -H "Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159" http://localhost:8080/api/settings
break
fi
if [ $i -eq 90 ]; then
echo "OpenAEV did not start in time, running mock tests only"
fi
echo "Attempt $i: OpenAEV not ready yet..."
docker logs openaev --tail 20 2>/dev/null || true
sleep 5
done
- name: Seed test data in OpenAEV
run: |
echo "Creating test data in OpenAEV..."
API_URL="http://localhost:8080/api"
AUTH_HEADER="Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159"
# Check if OpenAEV is available
if ! curl -s -H "$AUTH_HEADER" "$API_URL/settings" 2>/dev/null | grep -q "platform"; then
echo "OpenAEV not available, skipping data seeding"
exit 0
fi
# Create test Endpoints (agentless)
echo "Creating test Endpoints..."
curl -s -X POST "$API_URL/endpoints/agentless" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"asset_name": "Production Web Server",
"asset_description": "Main production web server",
"endpoint_platform": "Linux",
"endpoint_arch": "x86_64"
}' || true
curl -s -X POST "$API_URL/endpoints/agentless" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"asset_name": "Database Server",
"asset_description": "PostgreSQL database server",
"endpoint_platform": "Linux",
"endpoint_arch": "x86_64",
"endpoint_hostname": "db-server-01",
"endpoint_ips": ["192.168.1.100", "10.0.0.50"]
}' || true
curl -s -X POST "$API_URL/endpoints/agentless" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"asset_name": "Workstation Alpha",
"asset_description": "Developer workstation",
"endpoint_platform": "Windows",
"endpoint_arch": "x86_64",
"endpoint_hostname": "ws-alpha",
"endpoint_mac_addresses": ["00:1A:2B:3C:4D:5E"]
}' || true
# Create test Asset Groups
echo "Creating test Asset Groups..."
curl -s -X POST "$API_URL/asset_groups" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"asset_group_name": "Production Servers",
"asset_group_description": "All production environment servers"
}' || true
curl -s -X POST "$API_URL/asset_groups" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"asset_group_name": "Developer Workstations",
"asset_group_description": "Development team workstations"
}' || true
# Create test Teams
echo "Creating test Teams..."
curl -s -X POST "$API_URL/teams" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"team_name": "Red Team Alpha",
"team_description": "Offensive security team"
}' || true
curl -s -X POST "$API_URL/teams" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"team_name": "Blue Team",
"team_description": "Defensive security team"
}' || true
# Create test Attack Patterns
echo "Creating test Attack Patterns..."
curl -s -X POST "$API_URL/attack_patterns" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"attack_pattern_name": "Phishing",
"attack_pattern_external_id": "T1566",
"attack_pattern_stix_id": "attack-pattern--a62a8db3-f23a-4d8f-afd6-9dbc77e7813b",
"attack_pattern_description": "Phishing attack technique"
}' || true
curl -s -X POST "$API_URL/attack_patterns" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"attack_pattern_name": "Command and Scripting Interpreter",
"attack_pattern_external_id": "T1059",
"attack_pattern_stix_id": "attack-pattern--d1fcf083-a721-4223-aedf-bf8960798d62",
"attack_pattern_description": "Execution via command interpreter"
}' || true
curl -s -X POST "$API_URL/attack_patterns" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"attack_pattern_name": "PowerShell",
"attack_pattern_external_id": "T1059.001",
"attack_pattern_stix_id": "attack-pattern--970a3432-3237-47ad-bcca-7d8cbb217736",
"attack_pattern_description": "Execution via PowerShell"
}' || true
# Create test Scenarios
echo "Creating test Scenarios..."
curl -s -X POST "$API_URL/scenarios" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"scenario_name": "Ransomware Simulation",
"scenario_description": "Simulated ransomware attack scenario"
}' || true
curl -s -X POST "$API_URL/scenarios" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"scenario_name": "Lateral Movement Exercise",
"scenario_description": "Testing lateral movement detection"
}' || true
echo "Test data seeding completed!"
- name: Verify test data
run: |
echo "Verifying test data..."
API_URL="http://localhost:8080/api"
AUTH_HEADER="Authorization: Bearer bfa014e0-e02e-4aa6-a42b-603b19dcf159"
# Check if OpenAEV is available
if ! curl -s -H "$AUTH_HEADER" "$API_URL/settings" 2>/dev/null | grep -q "platform"; then
echo "OpenAEV not available, skipping verification"
exit 0
fi
echo "Querying Endpoints..."
curl -s -H "$AUTH_HEADER" "$API_URL/endpoints" || true
echo ""
echo "Querying Asset Groups..."
curl -s -H "$AUTH_HEADER" "$API_URL/asset_groups" || true
echo ""
echo "Querying Teams..."
curl -s -H "$AUTH_HEADER" "$API_URL/teams" || true
echo ""
echo "Querying Attack Patterns..."
curl -s -H "$AUTH_HEADER" "$API_URL/attack_patterns" || true
- name: Run OpenAEV integration tests with coverage
env:
OPENAEV_URL: http://localhost:8080
OPENAEV_TOKEN: bfa014e0-e02e-4aa6-a42b-603b19dcf159
run: npm run test:openaev -- --coverage
- name: Upload OpenAEV integration test coverage
uses: actions/upload-artifact@v7
if: always()
with:
name: coverage-openaev
path: coverage/coverage-final.json
retention-days: 1
- name: Cleanup
if: always()
run: |
docker stop openaev || true
docker rm openaev || true
# E2E tests with Playwright (with coverage collection)
e2e-tests:
name: E2E Tests (Playwright + Coverage)
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install Playwright browser and system dependencies
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- name: Install Playwright system dependencies (cache hit)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium
- name: Install xvfb
run: |
echo "Installing xvfb for headed Chrome..."
sudo apt-get update && sudo apt-get install -y xvfb
echo "xvfb install complete."
- name: Build extension with coverage instrumentation
run: npm run build:coverage
- name: Run Playwright E2E tests
# Use xvfb-run to provide a virtual display for headed Chrome (required for extensions)
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" npm run test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v7
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload Playwright results
uses: actions/upload-artifact@v7
if: always()
with:
name: playwright-results
path: playwright-results.json
retention-days: 7
- name: Upload E2E coverage
uses: actions/upload-artifact@v7
if: always()
with:
name: coverage-e2e
path: coverage/e2e/
retention-days: 1
# Upload combined coverage to Codecov
coverage:
name: Merge & Upload Coverage
runs-on: ubuntu-latest
needs: [lint, unit-tests, integration-opencti, integration-openaev, e2e-tests]
if: always()
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Download unit test coverage
uses: actions/download-artifact@v8
with:
name: coverage-unit
path: coverage/unit
continue-on-error: true
- name: Download OpenCTI coverage
uses: actions/download-artifact@v8
with:
name: coverage-opencti
path: coverage/opencti
continue-on-error: true
- name: Download OpenAEV coverage
uses: actions/download-artifact@v8
with:
name: coverage-openaev
path: coverage/openaev
continue-on-error: true
- name: Download E2E coverage
uses: actions/download-artifact@v8
with:
name: coverage-e2e
path: coverage/e2e
continue-on-error: true
- name: List coverage files
run: |
echo "Coverage files downloaded:"
find coverage -name "*.json" -type f 2>/dev/null || echo "No coverage files found"
- name: Merge coverage reports
run: npm run coverage:merge
continue-on-error: true
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: coverage/
flags: unittests,integration-opencti,integration-openaev,e2e
fail_ci_if_error: false
verbose: true