Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ef56488
chore: production cleanup and end-user test hardening
Opselon Oct 6, 2026
81f3d9b
test: add cross-database end-user E2E
Opselon Oct 6, 2026
4b17855
test: add SQLite and PostgreSQL cross-db compose
Opselon Oct 6, 2026
292d80e
test: expand CLI end-user E2E matrix
Opselon Oct 6, 2026
c283301
test: add dedicated Docker end-user E2E
Opselon Oct 6, 2026
69216aa
fix: make CLI matrix xunit data compile safely
Opselon Oct 6, 2026
f313eb0
test: assert exact labeler pull request trigger
Opselon Oct 6, 2026
132009b
fix: use valid anonymous payload for CLI E2E login
Opselon Oct 6, 2026
7ddc36a
test: remove obsolete labeler regression coverage
Opselon Oct 6, 2026
90bd565
chore: remove unused labeler workflow
Opselon Oct 6, 2026
f3b735e
test: add cross-platform CLI E2E matrix
Opselon Oct 6, 2026
66e6acd
test: add cross-platform filesystem runtime coverage
Opselon Oct 6, 2026
25e7519
test: remove obsolete labeler regression test
Opselon Oct 6, 2026
d6a1c46
fix: restore regression test class closure
Opselon Oct 6, 2026
97267dc
fix: restore release workflow regression helpers
Opselon Oct 6, 2026
4278601
fix: reach zero warnings/errors on latest base (a90c990)
Oct 6, 2026
c5b591b
fix(cli): reject unknown flags, fail closed on redirected stdin, vali…
Oct 6, 2026
153e1d7
fix(ci): make CLI E2E + Docker smoke tests actually pass
Oct 6, 2026
b9ec9c2
fix(cli): add process timeouts and PowerShell Core fallback for Windo…
Oct 6, 2026
e94103e
fix(cli): turn corrupted vault key and backup failures into controlle…
Oct 6, 2026
daa0c2b
test(e2e): include CLI stdout/stderr in backup assertion failure
Oct 6, 2026
3694a93
fix(secrets): retry the backup file move past transient Windows file …
Oct 6, 2026
2128916
diag: identify which backup call fails on Windows (temporary)
Oct 6, 2026
d2aedfa
fix(secrets): copy the staged backup instead of moving it on Windows
Oct 6, 2026
447ac1e
test(e2e): serialize Docker E2E collections and assert compose up suc…
Oct 6, 2026
6bec373
test(e2e): authenticate before provider probe, accept absolute login …
Oct 6, 2026
080809d
test(e2e): force fresh-install bootstrap path and read admin password
Oct 6, 2026
b8c1ad2
test(e2e): surface PUT /api/secrets error body on failure
Oct 6, 2026
80ff2af
fix(webapi): accept enum category names in JSON payloads
Oct 6, 2026
19b202c
fix(auth): invalidate outstanding cookies on logout; entrypoint owns …
Oct 6, 2026
6337c4f
fix(auth): bootstrap admin password was unreadable (BOM) and logout n…
Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ============================================
# ForexTradingBot - Environment Configuration
# ============================================
# Copy this file to .env and fill in your actual values.
# NEVER commit your real .env file to git!
#
# Values are intentionally left EMPTY here: every "example" string below a
# credential key tends to trip secret scanners. The commented lines show the
# format; paste your real values on the uncommented lines after copying.

# ============================================
# Telegram Bot Configuration
# ============================================
# From @BotFather:
# TELEGRAM_BOT_TOKEN=<paste your bot token>
TELEGRAM_BOT_TOKEN=

# Public HTTPS endpoint where Telegram sends updates:
# TELEGRAM_WEBHOOK_ADDRESS=https://your.domain/
TELEGRAM_WEBHOOK_ADDRESS=

# Shared secret used to sign Telegram webhook payloads:
# TELEGRAM_WEBHOOK_SECRET=<choose a long random string>
TELEGRAM_WEBHOOK_SECRET=

# Telegram User API (if you need user-level Telegram access)
# TELEGRAM_API_ID=<numeric api id>
# TELEGRAM_API_HASH=<api hash>
TELEGRAM_API_ID=
TELEGRAM_API_HASH=
TELEGRAM_PHONE_NUMBER=

# Admin user IDs (comma-separated Telegram user IDs allowed to control the bot)
ADMIN_USER_IDS=

# ============================================
# PostgreSQL Database Configuration
# ============================================
# POSTGRES_PASSWORD=<choose a strong password>
POSTGRES_USER=forexbot
POSTGRES_DB=forexbotdb
POSTGRES_PASSWORD=

# ============================================
# Crypto Pay Configuration
# ============================================
# CRYPTO_PAY_API_TOKEN=<paste your api token>
CRYPTO_PAY_API_TOKEN=
CRYPTO_PAY_BASE_URL=https://pay.crypt.bot/api/
CRYPTO_PAY_IS_TESTNET=true

# CRYPTO_PAY_WEBHOOK_SECRET=<choose a long random string>
CRYPTO_PAY_WEBHOOK_SECRET=

# ============================================
# Forwarding Rules (Optional)
# ============================================
# Format example (uncomment and fill in):
# FORWARDING_RULES__0__BotToken=<paste forwarding bot token>
# FORWARDING_RULES__0__SourceChannelId=-1001234567890
# FORWARDING_RULES__0__TargetChannelId=-1009876543210
# FORWARDING_RULES__0__IsEnabled=true

# ============================================
# Development Settings (Do NOT use in Production)
# ============================================
ASPNETCORE_ENVIRONMENT=Development
Logging__LogLevel__Default=Debug
Logging__LogLevel__Microsoft=Warning

# Docker sets this to a persistent application-data volume.
FOREXBOT_VAULT_DIRECTORY=
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Default owner for everything
* @Opselon
16 changes: 16 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## What does this PR do?

<!-- One-line summary -->

## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor
- [ ] CI / tooling
- [ ] Documentation

## Checklist
- [ ] `dotnet build ForexTradingBot.sln -c Release` passes
- [ ] `dotnet test ForexTradingBot.sln` passes
- [ ] No secrets/credentials committed (`.env`, tokens, keys)
- [ ] If Docker-related: `docker compose up` starts and `/healthz` returns 200
269 changes: 269 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true

jobs:
build-and-test:
name: Build & Test
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"
cache: true
cache-dependency-path: "**/*.csproj"

- name: Restore
run: dotnet restore ForexTradingBot.sln

- name: Dependency vulnerability gate
shell: bash
run: |
set -euo pipefail
report="$RUNNER_TEMP/vulnerable-packages.txt"
dotnet list ForexTradingBot.sln package --vulnerable --include-transitive 2>&1 | tee "$report"
if grep -Eqi 'has (the following )?vulnerabilit|severity:[[:space:]]*(critical|high|moderate|low)' "$report"; then
echo "::error::Known vulnerable NuGet package detected. Release is blocked."
exit 1
fi
- name: Build
run: dotnet build ForexTradingBot.sln -c Release --no-restore

- name: Test
run: >
dotnet test ForexTradingBot.sln -c Release --no-build
--logger "trx;LogFileName=test-results.trx"
--results-directory TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results
path: TestResults
retention-days: 14

e2e-end-user:
name: End-user E2E
runs-on: ubuntu-latest
timeout-minutes: 45
needs:
- build-and-test
- docker
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"

- name: Restore solution
run: dotnet restore ForexTradingBot.sln

- name: Restore E2E test project
run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj

- name: Build solution
run: dotnet build ForexTradingBot.sln -c Release --no-restore

- name: Build E2E test project
run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore

- name: Run end-user E2E
run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --logger "console;verbosity=normal"

- name: Upload E2E logs
if: always()
uses: actions/upload-artifact@v7
with:
name: end-user-e2e-logs
path: tests/e2e/.last.log
if-no-files-found: ignore
retention-days: 14

e2e-windows-cli:
name: Windows CLI E2E
runs-on: windows-latest
timeout-minutes: 20
needs: build-and-test
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"

- name: Restore solution
shell: pwsh
run: dotnet restore ForexTradingBot.sln

- name: Restore E2E test project
shell: pwsh
run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj

- name: Build solution
shell: pwsh
run: dotnet build ForexTradingBot.sln -c Release --no-restore

- name: Build E2E test project
shell: pwsh
run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore

- name: Run CLI E2E
shell: pwsh
run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --filter "Surface=CLI" --logger "console;verbosity=normal"

e2e-cli-platform-matrix:
name: CLI E2E (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 25
needs: build-and-test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"
- name: Restore
run: dotnet restore ForexTradingBot.sln
- name: Restore E2E project
run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj
- name: Build solution
run: dotnet build ForexTradingBot.sln -c Release --no-restore
- name: Build E2E project
run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore
- name: CLI E2E
run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --filter "Surface=CLI" --logger "console;verbosity=normal"

docker:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: Docker build & smoke
runs-on: ubuntu-latest
timeout-minutes: 30
needs: build-and-test
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Build image (load into local daemon)
uses: docker/build-push-action@v7
with:
context: .
push: false
load: true
tags: forexbot:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke test — real Postgres + Redis, API against them, /healthz = 200
run: |
set -euo pipefail
docker network create smokenet
docker run -d --name smoke-pg --network smokenet \
-e POSTGRES_USER=forexbot -e POSTGRES_PASSWORD=ci-smoke-password -e POSTGRES_DB=forexbotdb \
--health-cmd "pg_isready -U forexbot -d forexbotdb" \
--health-interval 5s --health-timeout 5s --health-retries 20 \
postgres:15-alpine
docker run -d --name smoke-redis --network smokenet redis:7-alpine
echo "Waiting for Postgres health..."
pg_ok=0
for i in $(seq 1 30); do
st=$(docker inspect -f '{{.State.Health.Status}}' smoke-pg 2>/dev/null || echo starting)
if [ "$st" = "healthy" ]; then echo "Postgres healthy after ${i} checks"; pg_ok=1; break; fi
sleep 5
done
if [ "$pg_ok" != "1" ]; then echo "::error::Postgres never became healthy"; docker logs smoke-pg | tail -50; docker rm -f smoke-api smoke-pg smoke-redis 2>/dev/null || true; docker network rm smokenet 2>/dev/null || true; exit 1; fi

# CI=true puts the app in smoke mode (wizard bypass + forced SQLite).
# To validate the REAL Postgres path we clear CI and set IsSmokeTest=false,
# so the app runs the same configuration a production container would.
docker rm -f smoke-api 2>/dev/null || true
docker run -d --name smoke-api --network smokenet \
-e CI=false \
-e IsSmokeTest=false \
-e DatabaseSettings__DatabaseProvider=postgres \
-e 'ConnectionStrings__DefaultConnection=Host=smoke-pg;Port=5432;Database=forexbotdb;Username=forexbot;Password=ci-smoke-password;Include Error Detail=true' \
-e ConnectionStrings__Redis=smoke-redis:6379,abortConnect=false \
-e TelegramPanel__BotToken= \
-e CryptoPay__ApiToken= \
-e ASPNETCORE_URLS=http://+:8080 \
-p 8080:8080 \
forexbot:ci
ok=0
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' -m 3 http://localhost:8080/healthz || true)
if [ "$code" = "200" ]; then
echo "API /healthz returned 200 after ${i} checks"
ok=1; break
fi
sleep 5
done
if [ "$ok" != "1" ]; then
echo "::error::API /healthz never returned 200"
docker logs smoke-api | tail -100
docker rm -f smoke-api smoke-pg smoke-redis || true
docker network rm smokenet || true
exit 1
fi
docker rm -f smoke-api smoke-pg smoke-redis || true
docker network rm smokenet || true

- name: Smoke test — SQLite variant
if: always()
run: |
set -euo pipefail
docker rm -f smoke-sqlite 2>/dev/null || true
docker run -d --name smoke-sqlite \
-e CI=false \
-e IsSmokeTest=false \
-e DatabaseSettings__DatabaseProvider=sqlite \
-e 'ConnectionStrings__DefaultConnection=Data Source=/tmp/forexbot.db' \
-e TelegramPanel__BotToken= \
-e CryptoPay__ApiToken= \
-e ASPNETCORE_URLS=http://+:8080 \
-p 8081:8080 \
forexbot:ci
ok=0
for i in $(seq 1 40); do
code=$(curl -s -o /dev/null -w '%{http_code}' -m 3 http://localhost:8081/healthz || true)
if [ "$code" = "200" ]; then
echo "SQLite variant /healthz returned 200 after ${i} checks"
ok=1; break
fi
sleep 5
done
docker logs smoke-sqlite | tail -60 || true
docker rm -f smoke-sqlite || true
[ "$ok" = "1" ] || { echo "::error::SQLite variant never became healthy"; exit 1; }
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Loading
Loading