ci: add PR quality and coverage gates

This commit is contained in:
2026-08-09 00:10:24 +08:00
parent e50ac83988
commit 2f90c82d54
34 changed files with 47 additions and 34 deletions
+16
View File
@@ -0,0 +1,16 @@
[run]
source = backend
omit =
backend/*/migrations/*
backend/*/test_*.py
backend/*/tests.py
backend/manage.py
backend/config/wsgi.py
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
if __name__ == .__main__.:
show_missing = true
skip_covered = true
+16 -4
View File
@@ -1,13 +1,17 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ gitea.event_name }}-${{ gitea.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
mysql:
image: mysql:8.0.35
@@ -45,6 +49,8 @@ jobs:
--timeout 120 \
--retries 5 \
-r requirements-dev.txt
- name: Ruff checks
run: .venv-ci/bin/ruff check backend scripts
- name: Check migrations
working-directory: backend
run: ../.venv-ci/bin/python manage.py makemigrations --check --dry-run
@@ -54,8 +60,14 @@ jobs:
- name: ASGI import check
working-directory: backend
run: ../.venv-ci/bin/python -c "from config.asgi import application; print(type(application).__name__)"
- name: Unit tests
run: .venv-ci/bin/python -m pytest -q
- name: SQLite tests and coverage
run: |
.venv-ci/bin/python -m pytest -q \
--cov=backend \
--cov-config=.coveragerc \
--cov-report=term \
--cov-report=xml:coverage.xml \
--cov-fail-under=75
- name: MySQL migrations and tests
env:
DATABASE_URL: mysql://root:ci-root-password@mysql:3306/hulumath
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -2,7 +2,6 @@ from django.urls import path
from .views import LoginView, LogoutView, MeView, RegisterView, VisitorMigrationView
urlpatterns = [
path("register/", RegisterView.as_view(), name="register"),
path("login/", LoginView.as_view(), name="login"),
-1
View File
@@ -1,3 +1,2 @@
from django.contrib import admin
# Register your models here.
-1
View File
@@ -1,7 +1,6 @@
import contextvars
import logging
request_id_context = contextvars.ContextVar("request_id", default="-")
@@ -3,7 +3,6 @@ import re
from django.core.management.base import BaseCommand, CommandError
from django.db import connection
MINIMUM_VERSION = (8, 0, 35)
-1
View File
@@ -1,3 +1,2 @@
from django.db import models
# Create your models here.
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-2
View File
@@ -7,13 +7,11 @@ from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import path
django_asgi_application = get_asgi_application()
from common.consumers import HealthConsumer
from contest.routing import websocket_urlpatterns
application = ProtocolTypeRouter(
{
"http": django_asgi_application,
-1
View File
@@ -4,7 +4,6 @@ from pathlib import Path
import dj_database_url
from django.core.exceptions import ImproperlyConfigured
BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_ROOT = BASE_DIR.parent
-1
View File
@@ -3,7 +3,6 @@ from django.urls import include, path
from common.views import health, home
admin.site.site_header = "葫芦数学运营后台"
admin.site.site_title = "葫芦数学"
admin.site.index_title = "内容与运营"
-1
View File
@@ -6,7 +6,6 @@ from progression.models import RewardTransaction, UserAbility
from .models import ContentInteraction, ContentItem, VideoProgress
ABILITY_TO_DIMENSION = {
ContentItem.Ability.VISION: UserAbility.Dimension.VISION,
ContentItem.Ability.HUMANITIES: UserAbility.Dimension.HUMANITIES,
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -8,7 +8,6 @@ from .views import (
VideoCompleteView,
)
urlpatterns = [
path("", ContentListView.as_view(), name="content-list"),
path("videos/catalog/", VideoCatalogView.as_view(), name="video-catalog"),
-1
View File
@@ -7,7 +7,6 @@ from rest_framework.views import APIView
from .models import ContentInteraction, ContentItem, VideoProgress
from .services import complete_video
ABILITY_META = {
ContentItem.Ability.VISION: {"label": "数学眼光", "icon": "", "color": "#5d73e8"},
ContentItem.Ability.HUMANITIES: {"label": "数学人文", "icon": "", "color": "#b76d38"},
@@ -2,7 +2,6 @@ from django.core.management.base import BaseCommand
from contest.models import Contest, ContestQuestion, Question, QuestionVersion
QUESTIONS = {
Question.Track.BEGINNER: [
("b-12-plus-19", "12 + 19", "31"),
-1
View File
@@ -2,7 +2,6 @@ from django.urls import path
from .consumers import MatchConsumer
websocket_urlpatterns = [
path("ws/v1/contest/matches/<uuid:match_id>/", MatchConsumer.as_asgi()),
]
+1
View File
@@ -6,6 +6,7 @@ from django.utils import timezone
from rest_framework.exceptions import ValidationError
from accounts.models import User
from .models import (
CheatFlag,
Contest,
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -9,7 +9,6 @@ from .views import (
MatchStateView,
)
urlpatterns = [
path("", ContestListView.as_view(), name="contest-list"),
path("<slug:slug>/start/", AttemptStartView.as_view(), name="attempt-start"),
-1
View File
@@ -1,3 +1,2 @@
from django.contrib import admin
# Register your models here.
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -1,3 +1,2 @@
from django.shortcuts import render
# Create your views here.
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -3,7 +3,6 @@ from rest_framework.routers import DefaultRouter
from .views import CourseListView, ExerciseSubmitView, FormulaDocumentViewSet
router = DefaultRouter()
router.register("documents", FormulaDocumentViewSet, basename="formula-document")
@@ -17,7 +17,6 @@ from math_life.models import (
)
from math_life.services import validate_story_content
DISCIPLINE_ICONS = {
"人工智能": "🤖",
"计算机": "💻",
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -9,7 +9,6 @@ from .views import (
StoryStartView,
)
urlpatterns = [
path("mathbti/", MathBTIAssessmentView.as_view(), name="mathbti-assessment"),
path("mathbti/submit/", MathBTISubmitView.as_view(), name="mathbti-submit"),
-1
View File
@@ -2,7 +2,6 @@ from django.contrib import admin
from .models import Card, RewardTransaction, UserAbility, UserCard, UserPet
admin.site.register(UserPet)
admin.site.register(UserAbility)
admin.site.register(Card)
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
-1
View File
@@ -2,7 +2,6 @@ from django.urls import path
from .views import ProgressionProfileView
urlpatterns = [
path("me/", ProgressionProfileView.as_view(), name="progression-profile"),
]
+1
View File
@@ -2,3 +2,4 @@
pytest==8.3.5
pytest-django==4.11.1
pytest-cov==6.2.1
ruff==0.11.13
+13
View File
@@ -0,0 +1,13 @@
target-version = "py311"
line-length = 100
src = ["backend"]
exclude = [
"backend/*/migrations",
"backend/staticfiles",
]
[lint]
select = ["E4", "E7", "E9", "F", "I"]
[lint.per-file-ignores]
"backend/config/asgi.py" = ["E402"]