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