Skip to content

Commit 3f08bd9

Browse files
committed
Add tests setup and first tests
1 parent 0e07708 commit 3f08bd9

File tree

6 files changed

+231
-0
lines changed

6 files changed

+231
-0
lines changed

massmigration/tests/__init__.py

Whitespace-only changes.

massmigration/tests/test_commands.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Standard library
2+
import os
3+
import shutil
4+
5+
# Third party
6+
from django.apps.registry import apps
7+
from django.core.management import execute_from_command_line
8+
from django.test import TestCase
9+
10+
# Mass Migration
11+
from massmigration.constants import MIGRATIONS_FOLDER
12+
13+
14+
class MakeMassMigrationTestCase(TestCase):
15+
""" Tests for the 'makemassmigration' command. """
16+
17+
def tearDown(self):
18+
super().tearDown()
19+
self.delete_massmigrations_folder()
20+
21+
def migrations_folder_path(self, app_name="massmigration"):
22+
""" The expected folder path where the command will put the migration files. """
23+
app = apps.get_app_config(app_name)
24+
return os.path.join(app.path, MIGRATIONS_FOLDER)
25+
26+
def delete_massmigrations_folder(self, app_name="massmigration"):
27+
""" Delete the folder which the command puts the migration files into. """
28+
path = self.migrations_folder_path(app_name)
29+
if os.path.exists(path):
30+
shutil.rmtree(path)
31+
32+
def get_migration_file(self, file_name, app_name="massmigration"):
33+
""" Return the file contents of the given migration file. """
34+
file_path = os.path.join(self.migrations_folder_path(app_name), file_name)
35+
with open(file_path) as file:
36+
return file.read()
37+
38+
def assert_migration_file_exists(self, file_name, app_name="massmigration"):
39+
file_path = os.path.join(self.migrations_folder_path(app_name), file_name)
40+
if not os.path.exists(file_path):
41+
self.fail(f"Migration file not found at {file_path}")
42+
43+
def run_command(self, file_name, *args, app_name="massmigration"):
44+
execute_from_command_line([
45+
"django-admin",
46+
"makemassmigration",
47+
app_name,
48+
file_name,
49+
*args,
50+
])
51+
52+
def test_increments_numbers(self):
53+
self.run_command("first_migration")
54+
self.assert_migration_file_exists("0001_first_migration.py")
55+
self.run_command("second_migration")
56+
self.assert_migration_file_exists("0002_second_migration.py")
57+
58+
def test_uses_correct_template(self):
59+
for index, (template, expected_base_class) in enumerate([
60+
("simple", "SimpleMigration"),
61+
("mapper", "MapperMigration"),
62+
("custom", "BaseMigration"),
63+
]):
64+
self.run_command("my_migration", "--template", template)
65+
file_contents = self.get_migration_file(f"000{index + 1}_my_migration.py")
66+
self.assertIn(f"\nclass Migration({expected_base_class}):\n", file_contents)

scripts/run_tests_locally.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
3+
""" Builds a Docker image locally to run the tests (if it's not already built), then uses it to run
4+
the massmigration tests.
5+
"""
6+
7+
import os
8+
import subprocess
9+
import sys
10+
11+
12+
IMAGE_NAME = "django-mass-migrations-test"
13+
14+
15+
def abort(msg):
16+
print(f"🚨 {msg}")
17+
sys.exit(1)
18+
19+
20+
def check_have_docker():
21+
try:
22+
subprocess.check_call(["which", "docker"])
23+
except subprocess.CalledProcessError:
24+
abort(
25+
"It looks like you don't have the 'docker' command installed. "
26+
"This utility script uses Docker to run the tests."
27+
)
28+
29+
30+
def project_folder():
31+
scripts_dir = os.path.dirname(os.path.abspath(__file__))
32+
return os.path.dirname(scripts_dir)
33+
34+
35+
def dockerfile_path():
36+
return os.path.join(project_folder(), "testing", "Dockerfile")
37+
38+
39+
def image_exists():
40+
output = subprocess.check_output(["docker", "images", "-q", IMAGE_NAME])
41+
return bool(output.strip())
42+
43+
44+
def build_image():
45+
subprocess.check_call([
46+
"docker",
47+
"build",
48+
"-t",
49+
IMAGE_NAME,
50+
"--file",
51+
dockerfile_path(),
52+
project_folder(),
53+
])
54+
55+
56+
def run_tests():
57+
subprocess.check_call(
58+
[
59+
"docker",
60+
"run",
61+
"--rm",
62+
"--volume",
63+
f"{project_folder()}/massmigration:/code/massmigration",
64+
IMAGE_NAME,
65+
*sys.argv[1:],
66+
],
67+
cwd=project_folder(),
68+
)
69+
70+
71+
def main():
72+
check_have_docker()
73+
if not image_exists():
74+
build_image()
75+
run_tests()
76+
77+
78+
if __name__ == '__main__':
79+
main()

testing/Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3.10-slim
2+
3+
ENV PYTHONUNBUFFERED 1
4+
5+
RUN mkdir /code
6+
WORKDIR /code
7+
COPY . /code/
8+
9+
# Install *this* package in editable mode. If we then mount the 'massmigration' folder as a volume
10+
# when running the Docker container, it should pick up local changes to the files.
11+
RUN pip install -e .
12+
COPY . /code/
13+
14+
ENV DJANGO_SETTINGS_MODULE "testing.test_settings"
15+
16+
ENTRYPOINT ["./testing/docker_entrypoint.sh"]

testing/docker_entrypoint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
# A shell script to be used as the ENTRYPOINT for the Docker container for running tests so that
4+
# the passed arguments (i.e. test name(s)) can be passed through to Django.
5+
6+
django-admin test massmigration $@

testing/test_settings.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
""" Barebones settings for running tests for 'massmigration' app. """
2+
3+
from pathlib import Path
4+
5+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
6+
BASE_DIR = Path(__file__).resolve().parent.parent
7+
8+
9+
SECRET_KEY = 'MASSMIGRATION_TEST_SECRET_KEY'
10+
11+
DEBUG = True
12+
13+
ALLOWED_HOSTS = []
14+
15+
16+
INSTALLED_APPS = [
17+
'django.contrib.admin',
18+
'django.contrib.auth',
19+
'django.contrib.contenttypes',
20+
'django.contrib.sessions',
21+
'django.contrib.messages',
22+
'django.contrib.staticfiles',
23+
'massmigration',
24+
]
25+
26+
MIDDLEWARE = [
27+
'django.middleware.security.SecurityMiddleware',
28+
'django.contrib.sessions.middleware.SessionMiddleware',
29+
'django.middleware.common.CommonMiddleware',
30+
'django.middleware.csrf.CsrfViewMiddleware',
31+
'django.contrib.auth.middleware.AuthenticationMiddleware',
32+
'django.contrib.messages.middleware.MessageMiddleware',
33+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
34+
]
35+
36+
ROOT_URLCONF = 'massmigration.urls'
37+
38+
TEMPLATES = [
39+
{
40+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
41+
'DIRS': [],
42+
'APP_DIRS': True,
43+
'OPTIONS': {
44+
'context_processors': [
45+
'django.template.context_processors.debug',
46+
'django.template.context_processors.request',
47+
'django.contrib.auth.context_processors.auth',
48+
'django.contrib.messages.context_processors.messages',
49+
],
50+
},
51+
},
52+
]
53+
54+
# WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
55+
56+
57+
DATABASES = {
58+
'default': {
59+
'ENGINE': 'django.db.backends.sqlite3',
60+
'NAME': ':memory:',
61+
}
62+
}
63+
64+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

0 commit comments

Comments
 (0)