Skip to content
Merged
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
15 changes: 11 additions & 4 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
description: "DEV operation"
type: choice
default: deploy
options: [deploy, verify_solution_rollback, prepare_solution_browser, finalize_solution_browser, verify_program_publication, grant_program_creator_11, dependency_backup, dependency_verify]
options: [deploy, verify_solution_rollback, prepare_solution_browser, finalize_solution_browser, verify_program_publication, grant_program_creator_11, dependency_backup, dependency_verify, prepare_pr517_closeout]
dependency_backup_key:
description: "Ключ резервной копии для проверки сохранности данных dev"
type: string
Expand All @@ -26,6 +26,10 @@ on:
description: "Existing user who owns the new QA programs"
default: "28"
type: string
apply_fixture:
description: "Apply the bounded PR517 fixture plan; false only previews it"
default: false
type: boolean

permissions:
contents: read
Expand Down Expand Up @@ -400,6 +404,7 @@ jobs:
VERIFY_OPERATION: ${{ inputs.operation }}
VERIFY_FIXTURE_KEY: ${{ inputs.fixture_key }}
VERIFY_MANAGER_ID: ${{ inputs.manager_user_id }}
VERIFY_FIXTURE_APPLY: ${{ inputs.apply_fixture && 'true' || 'false' }}
run: |
node <<'NODE'
const fs = require('node:fs');
Expand All @@ -408,10 +413,12 @@ jobs:
const operation = process.env.VERIFY_OPERATION;
const key = process.env.VERIFY_FIXTURE_KEY;
const manager = process.env.VERIFY_MANAGER_ID;
const phases = { verify_solution_rollback: 'verify_rollback', prepare_solution_browser: 'prepare_browser', finalize_solution_browser: 'finalize_browser', verify_program_publication: 'verify_publication', grant_program_creator_11: 'grant_creator_11' };
const applyFixture = process.env.VERIFY_FIXTURE_APPLY;
const phases = { verify_solution_rollback: 'verify_rollback', prepare_solution_browser: 'prepare_browser', finalize_solution_browser: 'finalize_browser', verify_program_publication: 'verify_publication', grant_program_creator_11: 'grant_creator_11', prepare_pr517_closeout: 'prepare_pr517_closeout' };
if (!/^[0-9a-f]{40}$/.test(sha) || !phases[operation] || !/^[a-z0-9-]{8,50}$/.test(key) || !/^[1-9][0-9]{0,8}$/.test(manager)) throw new Error('Invalid acceptance arguments');
if (!['true', 'false'].includes(applyFixture)) throw new Error('Invalid fixture apply flag');
const phase = phases[operation];
const file = phase === 'grant_creator_11' ? 'scripts/grant_dev_program_creation_20261027.py' : phase === 'verify_publication' ? 'scripts/verify_program_publication_dev.py' : phase === 'verify_rollback' ? 'scripts/verify_program_solutions_dev.py' : 'scripts/prepare_program_solutions_browser.py';
const file = phase === 'prepare_pr517_closeout' ? 'scripts/prepare_pr517_closeout.py' : phase === 'grant_creator_11' ? 'scripts/grant_dev_program_creation_20261027.py' : phase === 'verify_publication' ? 'scripts/verify_program_publication_dev.py' : phase === 'verify_rollback' ? 'scripts/verify_program_solutions_dev.py' : 'scripts/prepare_program_solutions_browser.py';
const source = fs.readFileSync(file, 'utf8');
const script = [
'set +x',
Expand All @@ -428,7 +435,7 @@ jobs:
'test "$(docker inspect -f \'{{.Image}}\' "$web")" = "$(docker inspect -f \'{{.Image}}\' "$worker")"',
'echo "VERIFIED_DEV_RUNTIME_REF=' + sha + '"',
'docker exec "$web" python manage.py migrate --check',
'docker exec -e VERIFY_EXPECTED_API_SHA=' + sha + ' -e VERIFY_EXPECTED_LEGACY_IDS=1,2,3,4,5,6,7,8,9,38,39 -e VERIFY_PHASE=' + phase + ' -e VERIFY_FIXTURE_KEY=' + key + ' -e VERIFY_MANAGER_ID=' + manager + ' -i "$web" python - <<\'SOLUTION_ACCEPTANCE_PY\'',
'docker exec -e VERIFY_EXPECTED_API_SHA=' + sha + ' -e VERIFY_EXPECTED_LEGACY_IDS=1,2,3,4,5,6,7,8,9,38,39 -e VERIFY_PHASE=' + phase + ' -e VERIFY_FIXTURE_KEY=' + key + ' -e VERIFY_MANAGER_ID=' + manager + ' -e VERIFY_FIXTURE_APPLY=' + applyFixture + ' -i "$web" python - <<\'SOLUTION_ACCEPTANCE_PY\'',
source,
'SOLUTION_ACCEPTANCE_PY'
].join('\n') + '\n';
Expand Down
114 changes: 114 additions & 0 deletions partner_programs/tests/test_pr517_closeout_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.core import mail
from django.test import TestCase

from partner_programs.certificate_models import ProgramCertificateAsset
from partner_programs.models import PartnerProgram, PartnerProgramProject
from partner_programs.solution_api import create_solution
from partner_programs.solution_models import ProgramSolution
from partner_programs.tests.helpers import (
create_partner_program,
create_project,
create_user,
)
from project_rates.tests.helpers import create_rate_expert
from scripts.prepare_pr517_closeout import prepare


class PR517CloseoutFixtureTests(TestCase):
def setUp(self):
self.manager = create_rate_expert()
self.expert = create_rate_expert()
self.captains = [
create_user(is_active=True),
self.expert,
create_user(is_active=True),
]
self.participant = create_user(is_active=True)
self.previous_project = create_project(name="Существующий проект")
self.previous_program = create_partner_program()
ProgramCertificateAsset.objects.create(
program=self.previous_program,
kind="signature",
name="existing.png",
content=b"existing-private-binary",
width=1,
height=1,
)
self.args = (
"test-fixture-20261103",
self.manager.pk,
[user.pk for user in self.captains],
[self.manager.pk, self.expert.pk],
self.participant.pk,
)

def test_preview_is_readonly_and_requires_existing_accounts(self):
counts = (get_user_model().objects.count(), PartnerProgram.objects.count())
result = prepare(*self.args)
self.assertFalse(result["applied"])
self.assertEqual(result["solution_count"], 23)
self.assertEqual(
(get_user_model().objects.count(), PartnerProgram.objects.count()), counts
)
with self.assertRaises(ValueError):
prepare(self.args[0], 90000000, *self.args[2:], apply=True)

def test_apply_is_private_bounded_and_preserves_existing_rows(self):
result = prepare(*self.args, apply=True)
self.assertEqual(
result["work_counts"],
{"legacy_project": 4, "solution": 21, "hackathon": 1, "noncompetitive": 1},
)
self.assertEqual(PartnerProgram.objects.filter(draft=True).count(), 4)
programs = [row["id"] for row in result["programs"]]
self.assertFalse(
PartnerProgram.objects.filter(
pk__in=programs, solution_creation_enabled=True
).exists()
)
self.assertEqual(ProgramSolution.objects.filter(status="submitted").count(), 4)
noncompetitive = PartnerProgram.objects.get(
pk=next(row["id"] for row in result["programs"] if not row["competitive"])
)
self.assertFalse(noncompetitive.criterias.exclude(type="str").exists())
self.assertFalse(noncompetitive.is_distributed_evaluation)
self.assertIsNone(noncompetitive.required_evaluators)
self.assertFalse(ProgramSolution.objects.exclude(published_at=None).exists())
self.assertEqual(
PartnerProgramProject.objects.filter(partner_program_id__in=programs).count(),
4,
)
self.assertTrue(
all(
not user.has_usable_password()
for user in get_user_model().objects.filter(
pk__in=result["synthetic_user_ids"]
)
)
)
self.previous_project.refresh_from_db()
self.assertEqual(self.previous_project.name, "Существующий проект")
self.assertEqual(getattr(mail, "outbox", []), [])
with self.assertRaises(ValueError):
prepare(*self.args, apply=True)

def test_existing_data_change_rolls_back_the_entire_fixture(self):
counts = (get_user_model().objects.count(), PartnerProgram.objects.count())

def corrupt(*args, **kwargs):
type(self.previous_project).objects.filter(
pk=self.previous_project.pk
).update(name="Ошибка")
return create_solution(*args, **kwargs)

with patch("partner_programs.solution_api.create_solution", side_effect=corrupt):
with self.assertRaisesRegex(AssertionError, "Existing data changed"):
prepare(*self.args, apply=True)
self.previous_project.refresh_from_db()
self.assertEqual(self.previous_project.name, "Существующий проект")
self.assertEqual(
(get_user_model().objects.count(), PartnerProgram.objects.count()), counts
)
Loading
Loading