Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
99 changes: 99 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
frontend-tests:
name: Frontend Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies
run: |
cd frontend
npm ci

- name: Run frontend tests
run: |
cd frontend
npm run test -- --run

backend-tests:
name: Backend Tests
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: kernelboard_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379

steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
cache: 'pip'

- name: Install dependencies
run: |
pip install -r requirements.txt
pip install -e .

- name: Run backend tests
env:
CI: true
REDIS_URL: redis://localhost:6379/0
DATABASE_URL: postgresql://postgres:test@localhost:5432/kernelboard_test
DISCORD_CLIENT_ID: test
DISCORD_CLIENT_SECRET: test
SECRET_KEY: test-secret-key
DISCORD_CLUSTER_MANAGER_API_BASE_URL: http://test.example.com
run: |
pytest -v

- name: Run coverage
env:
CI: true
REDIS_URL: redis://localhost:6379/0
DATABASE_URL: postgresql://postgres:test@localhost:5432/kernelboard_test
DISCORD_CLIENT_ID: test
DISCORD_CLIENT_SECRET: test
SECRET_KEY: test-secret-key
DISCORD_CLUSTER_MANAGER_API_BASE_URL: http://test.example.com
run: |
coverage run -m pytest
coverage report -m
19 changes: 13 additions & 6 deletions kernelboard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ def unauthorized():

app.register_blueprint(health.blueprint)
app.add_url_rule("/health", endpoint="health")

@app.route("/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm changes here seem sus

def root():
# permanent redirect
return redirect("/v2", code=302)
# Register the main blueprints for backward compatibility and testing
app.register_blueprint(index.blueprint)
app.register_blueprint(leaderboard.blueprint)
app.register_blueprint(news.blueprint)

if not app.blueprints.get("api"):
api = create_api_blueprint()
Expand All @@ -125,7 +125,14 @@ def unauthorized(_error):

@app.errorhandler(404)
def not_found(_error):
return redirect("/v2/404")
# Only redirect to React frontend for v2 routes or unhandled routes
# Let backend routes (like /leaderboard/123) return proper 404s
from flask import request
if request.path.startswith('/v2/') or request.path == '/':
return redirect("/v2/404")
else:
# Let the backend route handle the 404 properly
return _error

@app.errorhandler(500)
def server_error(_error):
Expand Down
4 changes: 2 additions & 2 deletions kernelboard/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
<div class="header-container">
<div class="header-layout">
<div class="header-row">
<a href="{{ url_for('index') }}" class="header-brand-item">
<a href="{{ url_for('index.index') }}" class="header-brand-item">
<span class="header-brand-icon">⚡</span>
<span>GPU MODE</span>
</a>
<div class="header-item">
<a href="{{ url_for('news') }}" class="header-link">
<a href="{{ url_for('news.news') }}" class="header-link">
News
</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion kernelboard/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h1>Leaderboards</h1>

<div class="leaderboard-grid">
{% for leaderboard in leaderboards %}
<a href="{{ url_for('leaderboard', id=leaderboard['id']) }}" class="leaderboard-tile-link">
<a href="{{ url_for('leaderboard.leaderboard', leaderboard_id=leaderboard['id']) }}" class="leaderboard-tile-link">
<div class="leaderboard-tile">
<div class="leaderboard-tile-name">
{% set color = leaderboard['name']|to_color %}
Expand Down
58 changes: 57 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import subprocess
import time
import secrets
import os


def get_test_redis_url(port: int):
Expand All @@ -28,6 +29,11 @@ def get_test_db_info():
}


def is_ci_environment():
"""Check if we're running in a CI environment where Docker may not be available."""
return os.environ.get("CI") == "true" or os.environ.get("GITHUB_ACTIONS") == "true"


def _short_random_string() -> str:
"""Returns a random string of 6 lowercase letters."""
return "".join(random.choice(string.ascii_lowercase) for i in range(6))
Expand All @@ -50,7 +56,44 @@ def _execute_sql(url: str, sql: str):
@pytest.fixture(scope="session")
def db_server():
"""Starts a DB server and creates a template DB once per session."""


# In CI environments, use the service containers provided by GitHub Actions
if is_ci_environment():
# GitHub Actions provides PostgreSQL service container
db_url = "postgresql://postgres:test@localhost:5432"

# Create a template database and load data
template_db = f"kernelboard_template_{_short_random_string()}"
_execute_sql(db_url, f"CREATE DATABASE {template_db}")

# Load data.sql into the template database using psql
result = subprocess.run(
[
"psql",
"-h",
"localhost",
"-U",
"postgres",
"-p",
"5432",
"-d",
template_db,
"-f",
"tests/data.sql",
],
env={"PGPASSWORD": "test"},
)

if result.returncode != 0:
pytest.exit("Error loading data.sql in CI", returncode=1)

yield {"db_url": db_url, "db_name": template_db}

# Cleanup: drop the template database
_execute_sql(db_url, f"DROP DATABASE {template_db}")
return

# Original Docker-based logic for local development
container_name = f"kernelboard_db_{_short_random_string()}"

test_db = get_test_db_info()
Expand Down Expand Up @@ -166,7 +209,14 @@ def db_server():
def redis_server():
"""
Starts a Redis Docker container for the test session.
In CI environments, uses the service container provided by GitHub Actions.
"""

# In CI environments, use the service containers provided by GitHub Actions
if is_ci_environment():
yield "redis://localhost:6379/0"
return

container_name = f"kernelboard_redis_{_short_random_string()}"
port = get_test_redis_port()
redis_url = get_test_redis_url(port)
Expand Down Expand Up @@ -301,6 +351,12 @@ def runner(app):

@pytest.fixture(autouse=True)
def set_env(monkeypatch):
# In CI environments, rely on environment variables set by GitHub Actions
if is_ci_environment():
# Don't override environment variables in CI mode - use what's already set
return

# For local development, set the traditional test environment variables
monkeypatch.setenv("DATABASE_URL", get_test_db_info()["db_url"])
monkeypatch.setenv("DISCORD_CLIENT_ID", "test")
monkeypatch.setenv("DISCORD_CLIENT_SECRET", "test")
Expand Down