Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4adf295c89 |
No files matched your search
@@ -0,0 +1,31 @@
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||
# See: https://circleci.com/docs/reference/configuration-reference/
|
||||
version: 2.1
|
||||
|
||||
# Define a job to be invoked later in a workflow.
|
||||
# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs
|
||||
jobs:
|
||||
say-hello:
|
||||
# Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
|
||||
# See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job
|
||||
docker:
|
||||
# Specify the version you desire here
|
||||
# See: https://circleci.com/developer/images/image/cimg/base
|
||||
- image: cimg/base:current
|
||||
|
||||
# Add steps to the job
|
||||
# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps
|
||||
steps:
|
||||
# Checkout the code as the first step.
|
||||
- checkout
|
||||
- run:
|
||||
name: "Say hello"
|
||||
command: "echo Hello, World!"
|
||||
|
||||
# Orchestrate jobs using workflows
|
||||
# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows
|
||||
workflows:
|
||||
say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow.
|
||||
# Inside the workflow, you define the jobs you want to run.
|
||||
jobs:
|
||||
- say-hello
|
||||
@@ -1,15 +1,26 @@
|
||||
name: Database migrations
|
||||
'on':
|
||||
pull_request: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "db/**"
|
||||
- "pom.xml"
|
||||
- ".github/workflows/db-migrations.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
contents: read
|
||||
branches: [main]
|
||||
paths:
|
||||
- "db/**"
|
||||
- "pom.xml"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
uses: QRun-IO/qqq-orb/.github/workflows/business-db.yml@5bcec90e330b1b4d7ec416d64b065e0aed74a6ff
|
||||
concurrency:
|
||||
group: business-db-${{ github.workflow }}-${{ github.event.pull_request.number ||
|
||||
github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
empty-database-migration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
cache: maven
|
||||
- name: Empty PostgreSQL -> migrate -> validate, demo determinism, forward-only policy
|
||||
run: mvn -B -pl db verify
|
||||
+109
-9
@@ -1,19 +1,119 @@
|
||||
name: Required E2E validation
|
||||
'on':
|
||||
pull_request: null
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: 17 6 * * *
|
||||
workflow_dispatch: null
|
||||
- cron: "17 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: e2e-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
uses: QRun-IO/qqq-orb/.github/workflows/business-e2e.yml@0965a4565369a74650c8f96683b2296435a35382
|
||||
secrets:
|
||||
e2e:
|
||||
name: E2E / live services
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
CI: "true"
|
||||
E2E_BASE_URL: http://localhost:3004
|
||||
E2E_API_URL: http://localhost:18080
|
||||
E2E_KEYCLOAK_URL: http://localhost:18081
|
||||
NEXTAUTH_URL: http://localhost:3004
|
||||
NEXTAUTH_SECRET: ci-only-e2e-secret-not-for-production
|
||||
KEYCLOAK_ISSUER: http://localhost:18081/realms/qqq-platform
|
||||
KEYCLOAK_CLIENT_ID: platform-web
|
||||
KEYCLOAK_CLIENT_SECRET: platform-web-secret
|
||||
API_BASE_URL_INTERNAL: http://localhost:18080
|
||||
E2E_ALERT_WEBHOOK_URL: ${{ secrets.E2E_ALERT_WEBHOOK_URL }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
cache: maven
|
||||
|
||||
- name: Install web dependencies and Chromium
|
||||
working-directory: web
|
||||
run: |
|
||||
npm ci
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Start actual platform dependencies and API
|
||||
run: docker compose -f deploy/compose/docker-compose.yml up -d --build postgres migrate keycloak minio minio-setup mailpit qqq-server
|
||||
|
||||
- name: Wait for actual services
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
wait_for() {
|
||||
local name="$1" url="$2"
|
||||
for attempt in {1..90}; do
|
||||
if curl --fail --silent --show-error "$url" >/dev/null; then
|
||||
echo "$name is ready"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "::error::$name did not become ready at $url"
|
||||
docker compose -f deploy/compose/docker-compose.yml ps
|
||||
return 1
|
||||
}
|
||||
wait_for Keycloak "$E2E_KEYCLOAK_URL/realms/qqq-platform/.well-known/openid-configuration"
|
||||
wait_for API "$E2E_API_URL/api/public/v1/health"
|
||||
|
||||
- name: Run blocking E2E suite
|
||||
working-directory: web
|
||||
run: npm run test:e2e:ci
|
||||
|
||||
- name: Publish test summary
|
||||
if: always()
|
||||
working-directory: web
|
||||
run: node scripts/report-e2e-results.mjs
|
||||
|
||||
- name: Upload Playwright evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-results-${{ github.run_id }}
|
||||
path: |
|
||||
web/playwright-report/
|
||||
web/test-results/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
- name: Notify configured alert webhook
|
||||
if: failure() && env.E2E_ALERT_WEBHOOK_URL != ''
|
||||
run: |
|
||||
curl --fail-with-body -X POST -H 'Content-Type: application/json' \
|
||||
--data "{\"text\":\"E2E validation failed for $GITHUB_REPOSITORY ($GITHUB_REF_NAME): $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"}" \
|
||||
"$E2E_ALERT_WEBHOOK_URL"
|
||||
|
||||
- name: Show service logs after failure
|
||||
if: failure()
|
||||
run: docker compose -f deploy/compose/docker-compose.yml logs --no-color --tail=300
|
||||
|
||||
- name: Stop services
|
||||
if: always()
|
||||
run: docker compose -f deploy/compose/docker-compose.yml down -v
|
||||
|
||||
deployment-gate:
|
||||
name: Deployment gate / E2E passed
|
||||
needs: e2e
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Deployment is permitted only because the live-service E2E job passed."
|
||||
@@ -19,10 +19,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
class ExternalPostgresMigrationIT {
|
||||
private static final Set<String> TABLES = Set.of(
|
||||
"newsletter_subscription", "newsletter_segment", "newsletter_campaign",
|
||||
"outbound_email", "email_suppression", "opt_in_token",
|
||||
"user_profile", "local_registration", "cms_site", "cms_asset", "cms_author",
|
||||
"cms_page", "cms_blog_post", "cms_tag", "cms_category", "cms_navigation",
|
||||
"cms_redirect", "cms_content_revision", "cms_blog_post_category", "cms_blog_post_tag");
|
||||
"outbound_email", "email_suppression", "opt_in_token");
|
||||
|
||||
@Test
|
||||
void emptyDatabaseMigratesValidatesAndSeedsDeterministically() throws Exception {
|
||||
@@ -41,7 +38,7 @@ class ExternalPostgresMigrationIT {
|
||||
liquibase.update(new Contexts("baseline,demo"), new LabelExpression());
|
||||
liquibase.update(new Contexts("baseline,demo"), new LabelExpression());
|
||||
assertEquals(1, count(connection, "newsletter_subscription"));
|
||||
assertEquals(7, count(connection, "databasechangelog"));
|
||||
assertEquals(3, count(connection, "databasechangelog"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,20 +20,17 @@ import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@DisabledIfEnvironmentVariable(named = "QQQ_EXTERNAL_DB_URL", matches = ".+")
|
||||
class NewsletterMigrationIT {
|
||||
|
||||
private static final String CHANGELOG = "db/changelog/db.changelog-master.yaml";
|
||||
private static final Set<String> PLATFORM_TABLES = Set.of(
|
||||
private static final Set<String> NEWSLETTER_TABLES = Set.of(
|
||||
"newsletter_subscription", "newsletter_segment", "newsletter_campaign",
|
||||
"outbound_email", "email_suppression", "opt_in_token",
|
||||
"user_profile", "local_registration", "cms_site", "cms_asset", "cms_author",
|
||||
"cms_page", "cms_blog_post", "cms_tag", "cms_category", "cms_navigation",
|
||||
"cms_redirect", "cms_content_revision", "cms_blog_post_category", "cms_blog_post_tag");
|
||||
"outbound_email", "email_suppression", "opt_in_token");
|
||||
|
||||
@Container
|
||||
final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
|
||||
static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:16-alpine")
|
||||
.withDatabaseName("qqq")
|
||||
.withUsername("qqq")
|
||||
.withPassword("qqq");
|
||||
@@ -45,10 +42,10 @@ class NewsletterMigrationIT {
|
||||
liquibase.update(new Contexts("baseline"), new LabelExpression());
|
||||
liquibase.validate();
|
||||
|
||||
assertEquals(PLATFORM_TABLES, applicationTables(connection));
|
||||
assertEquals(NEWSLETTER_TABLES, applicationTables(connection));
|
||||
assertEquals(0, count(connection, "newsletter_subscription"));
|
||||
assertEquals(0, count(connection, "newsletter_campaign"));
|
||||
assertEquals(6, appliedChangeSets(connection));
|
||||
assertEquals(2, appliedChangeSets(connection));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +59,16 @@ class NewsletterMigrationIT {
|
||||
assertEquals(1, count(connection, "newsletter_subscription"));
|
||||
assertEquals(1, count(connection, "newsletter_segment"));
|
||||
assertEquals(1, count(connection, "newsletter_campaign"));
|
||||
assertEquals(7, appliedChangeSets(connection));
|
||||
assertEquals(3, appliedChangeSets(connection));
|
||||
|
||||
liquibase.update(new Contexts("baseline,demo"), new LabelExpression());
|
||||
assertEquals(1, count(connection, "newsletter_subscription"));
|
||||
assertEquals(7, appliedChangeSets(connection));
|
||||
assertEquals(3, appliedChangeSets(connection));
|
||||
}
|
||||
}
|
||||
|
||||
private Connection connect() throws Exception {
|
||||
return DriverManager.getConnection(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
|
||||
private static Connection connect() throws Exception {
|
||||
return DriverManager.getConnection(POSTGRES.getJdbcUrl(), POSTGRES.getUsername(), POSTGRES.getPassword());
|
||||
}
|
||||
|
||||
private static Liquibase liquibase(Connection connection) throws Exception {
|
||||
|
||||
Reference in new issue
Block a user