From 2f90c82d54f2d63e65882bae67026ef38d2be0ca Mon Sep 17 00:00:00 2001 From: Jacky Date: Sun, 9 Aug 2026 00:10:24 +0800 Subject: [PATCH] ci: add PR quality and coverage gates --- .coveragerc | 16 +++++++++++++++ .gitea/workflows/ci.yml | 20 +++++++++++++++---- backend/accounts/tests.py | 1 - backend/accounts/urls.py | 1 - backend/common/admin.py | 1 - backend/common/logging.py | 1 - .../common/management/commands/check_mysql.py | 1 - backend/common/models.py | 1 - backend/common/tests.py | 1 - backend/config/asgi.py | 2 -- backend/config/settings.py | 1 - backend/config/urls.py | 1 - backend/content/services.py | 1 - backend/content/tests.py | 1 - backend/content/urls.py | 1 - backend/content/views.py | 1 - .../management/commands/seed_contests.py | 1 - backend/contest/routing.py | 1 - backend/contest/services.py | 1 + backend/contest/tests.py | 1 - backend/contest/urls.py | 1 - backend/engagement/admin.py | 1 - backend/engagement/tests.py | 1 - backend/engagement/views.py | 1 - backend/latex_lab/tests.py | 1 - backend/latex_lab/urls.py | 1 - .../commands/seed_initial_content.py | 1 - backend/math_life/tests.py | 1 - backend/math_life/urls.py | 1 - backend/progression/admin.py | 1 - backend/progression/tests.py | 1 - backend/progression/urls.py | 1 - requirements-dev.txt | 1 + ruff.toml | 13 ++++++++++++ 34 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 .coveragerc create mode 100644 ruff.toml diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..b41a20f --- /dev/null +++ b/.coveragerc @@ -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 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d4e5b4e..285dd68 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/backend/accounts/tests.py b/backend/accounts/tests.py index 7ce503c..4929020 100644 --- a/backend/accounts/tests.py +++ b/backend/accounts/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/accounts/urls.py b/backend/accounts/urls.py index 152a432..91dafa2 100644 --- a/backend/accounts/urls.py +++ b/backend/accounts/urls.py @@ -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"), diff --git a/backend/common/admin.py b/backend/common/admin.py index 8c38f3f..b97a94f 100644 --- a/backend/common/admin.py +++ b/backend/common/admin.py @@ -1,3 +1,2 @@ -from django.contrib import admin # Register your models here. diff --git a/backend/common/logging.py b/backend/common/logging.py index fb53a2c..6f5e032 100644 --- a/backend/common/logging.py +++ b/backend/common/logging.py @@ -1,7 +1,6 @@ import contextvars import logging - request_id_context = contextvars.ContextVar("request_id", default="-") diff --git a/backend/common/management/commands/check_mysql.py b/backend/common/management/commands/check_mysql.py index 5db39cb..b197020 100644 --- a/backend/common/management/commands/check_mysql.py +++ b/backend/common/management/commands/check_mysql.py @@ -3,7 +3,6 @@ import re from django.core.management.base import BaseCommand, CommandError from django.db import connection - MINIMUM_VERSION = (8, 0, 35) diff --git a/backend/common/models.py b/backend/common/models.py index 71a8362..35e0d64 100644 --- a/backend/common/models.py +++ b/backend/common/models.py @@ -1,3 +1,2 @@ -from django.db import models # Create your models here. diff --git a/backend/common/tests.py b/backend/common/tests.py index 7ce503c..4929020 100644 --- a/backend/common/tests.py +++ b/backend/common/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/config/asgi.py b/backend/config/asgi.py index b04a7aa..f1784c0 100644 --- a/backend/config/asgi.py +++ b/backend/config/asgi.py @@ -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, diff --git a/backend/config/settings.py b/backend/config/settings.py index 8a2caeb..509f7b3 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -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 diff --git a/backend/config/urls.py b/backend/config/urls.py index 15fd2c0..3a6c2e1 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -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 = "内容与运营" diff --git a/backend/content/services.py b/backend/content/services.py index 7c090f0..a9a1028 100644 --- a/backend/content/services.py +++ b/backend/content/services.py @@ -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, diff --git a/backend/content/tests.py b/backend/content/tests.py index 7ce503c..4929020 100644 --- a/backend/content/tests.py +++ b/backend/content/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/content/urls.py b/backend/content/urls.py index 8c41995..a46c961 100644 --- a/backend/content/urls.py +++ b/backend/content/urls.py @@ -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"), diff --git a/backend/content/views.py b/backend/content/views.py index af2cf62..9717a88 100644 --- a/backend/content/views.py +++ b/backend/content/views.py @@ -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"}, diff --git a/backend/contest/management/commands/seed_contests.py b/backend/contest/management/commands/seed_contests.py index a9c08a1..4fe746e 100644 --- a/backend/contest/management/commands/seed_contests.py +++ b/backend/contest/management/commands/seed_contests.py @@ -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"), diff --git a/backend/contest/routing.py b/backend/contest/routing.py index 9b8677a..3bcf2d9 100644 --- a/backend/contest/routing.py +++ b/backend/contest/routing.py @@ -2,7 +2,6 @@ from django.urls import path from .consumers import MatchConsumer - websocket_urlpatterns = [ path("ws/v1/contest/matches//", MatchConsumer.as_asgi()), ] diff --git a/backend/contest/services.py b/backend/contest/services.py index 211cecc..02073fc 100644 --- a/backend/contest/services.py +++ b/backend/contest/services.py @@ -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, diff --git a/backend/contest/tests.py b/backend/contest/tests.py index 7ce503c..4929020 100644 --- a/backend/contest/tests.py +++ b/backend/contest/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/contest/urls.py b/backend/contest/urls.py index a0fcadb..dcf9ae5 100644 --- a/backend/contest/urls.py +++ b/backend/contest/urls.py @@ -9,7 +9,6 @@ from .views import ( MatchStateView, ) - urlpatterns = [ path("", ContestListView.as_view(), name="contest-list"), path("/start/", AttemptStartView.as_view(), name="attempt-start"), diff --git a/backend/engagement/admin.py b/backend/engagement/admin.py index 8c38f3f..b97a94f 100644 --- a/backend/engagement/admin.py +++ b/backend/engagement/admin.py @@ -1,3 +1,2 @@ -from django.contrib import admin # Register your models here. diff --git a/backend/engagement/tests.py b/backend/engagement/tests.py index 7ce503c..4929020 100644 --- a/backend/engagement/tests.py +++ b/backend/engagement/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/engagement/views.py b/backend/engagement/views.py index 91ea44a..b8e4ee0 100644 --- a/backend/engagement/views.py +++ b/backend/engagement/views.py @@ -1,3 +1,2 @@ -from django.shortcuts import render # Create your views here. diff --git a/backend/latex_lab/tests.py b/backend/latex_lab/tests.py index 7ce503c..4929020 100644 --- a/backend/latex_lab/tests.py +++ b/backend/latex_lab/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/latex_lab/urls.py b/backend/latex_lab/urls.py index 4703920..82d21fb 100644 --- a/backend/latex_lab/urls.py +++ b/backend/latex_lab/urls.py @@ -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") diff --git a/backend/math_life/management/commands/seed_initial_content.py b/backend/math_life/management/commands/seed_initial_content.py index 73daf0f..71956ba 100644 --- a/backend/math_life/management/commands/seed_initial_content.py +++ b/backend/math_life/management/commands/seed_initial_content.py @@ -17,7 +17,6 @@ from math_life.models import ( ) from math_life.services import validate_story_content - DISCIPLINE_ICONS = { "人工智能": "🤖", "计算机": "💻", diff --git a/backend/math_life/tests.py b/backend/math_life/tests.py index 7ce503c..4929020 100644 --- a/backend/math_life/tests.py +++ b/backend/math_life/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/math_life/urls.py b/backend/math_life/urls.py index 98fe39f..dc997b2 100644 --- a/backend/math_life/urls.py +++ b/backend/math_life/urls.py @@ -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"), diff --git a/backend/progression/admin.py b/backend/progression/admin.py index 90a8d2b..a6bce6e 100644 --- a/backend/progression/admin.py +++ b/backend/progression/admin.py @@ -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) diff --git a/backend/progression/tests.py b/backend/progression/tests.py index 7ce503c..4929020 100644 --- a/backend/progression/tests.py +++ b/backend/progression/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/backend/progression/urls.py b/backend/progression/urls.py index 96055a3..ed0424a 100644 --- a/backend/progression/urls.py +++ b/backend/progression/urls.py @@ -2,7 +2,6 @@ from django.urls import path from .views import ProgressionProfileView - urlpatterns = [ path("me/", ProgressionProfileView.as_view(), name="progression-profile"), ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 823d167..2e33cf1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,4 @@ pytest==8.3.5 pytest-django==4.11.1 pytest-cov==6.2.1 +ruff==0.11.13 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..99d0675 --- /dev/null +++ b/ruff.toml @@ -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"]