Skip to content

DX: Cleanup tests #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
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
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ repos:
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
- id: no-commit-to-branch
- id: trailing-whitespace

- repo: https://github.com/crate-ci/typos
rev: v1.32.0
hooks:
- id: typos
args: []
types_or:
- python

- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ In your model :

``` python
class DbState(models.Model):
id = models.CharField(primary_key=True, max_length=50)
label = models.CharField(max_length=255)
id = models.CharField(primary_key=True)
label = models.CharField()

def __str__(self):
return self.label
Expand Down
3 changes: 0 additions & 3 deletions django_fsm/models.py

This file was deleted.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ extend-ignore = [
"B",
"PTH",
"ANN", # Missing type annotation
"S101", # Use of `assert` detected
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
"PT", # Use a regular `assert` instead of unittest-style
"DJ008", # Model does not define `__str__` method
"ARG001", # Unused function argument
"ARG002", # Unused method argument
"TRY002", # Create your own exception
Expand All @@ -84,6 +83,10 @@ fixable = [
"RUF100", # Unused `noqa` directive
]

[tool.ruff.lint.extend-per-file-ignores]
"tests/*" = [
"DJ008", # Model does not define `__str__` method
]

[tool.ruff.lint.isort]
force-single-line = true
Expand Down
4 changes: 2 additions & 2 deletions tests/testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def draft(self):
pass

@transition(field=state, source=["new", "draft"], target="dept")
def to_approvement(self):
def submitted(self):
pass

@transition(field=state, source="dept", target="dean")
Expand Down Expand Up @@ -53,7 +53,7 @@ def draft(self):
pass

@transition(field=state, source=["new", "draft"], target="dept")
def to_approvement(self):
def submitted(self):
pass

@transition(field=state, source="dept", target="dean")
Expand Down
14 changes: 7 additions & 7 deletions tests/testapp/tests/test_abstract_inheritance.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ def setUp(self):
self.model = InheritedFromAbstractModel()

def test_known_transition_should_succeed(self):
self.assertTrue(can_proceed(self.model.publish))
assert can_proceed(self.model.publish)
self.model.publish()
self.assertEqual(self.model.state, "published")
assert self.model.state == "published"

self.assertTrue(can_proceed(self.model.stick))
assert can_proceed(self.model.stick)
self.model.stick()
self.assertEqual(self.model.state, "sticked")
assert self.model.state == "sticked"

def test_field_available_transitions_works(self):
self.model.publish()
self.assertEqual(self.model.state, "published")
assert self.model.state == "published"
transitions = self.model.get_available_state_transitions()
self.assertEqual(["sticked"], [data.target for data in transitions])
assert [data.target for data in transitions] == ["sticked"]

def test_field_all_transitions_works(self):
transitions = self.model.get_all_state_transitions()
self.assertEqual({("new", "published"), ("published", "sticked")}, {(data.source, data.target) for data in transitions})
assert {("new", "published"), ("published", "sticked")} == {(data.source, data.target) for data in transitions}
8 changes: 4 additions & 4 deletions tests/testapp/tests/test_access_deferred_fsm_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ def setUp(self):
self.model = DeferrableModel.objects.only("id").get()

def test_usecase(self):
self.assertEqual(self.model.state, "new")
self.assertTrue(can_proceed(self.model.remove))
assert self.model.state == "new"
assert can_proceed(self.model.remove)
self.model.remove()

self.assertEqual(self.model.state, "removed")
self.assertFalse(can_proceed(self.model.remove))
assert self.model.state == "removed"
assert not can_proceed(self.model.remove)
84 changes: 45 additions & 39 deletions tests/testapp/tests/test_basic_transitions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import pytest
from django.db import models
from django.test import TestCase

Expand Down Expand Up @@ -53,70 +54,74 @@ def setUp(self):
self.model = SimpleBlogPost()

def test_initial_state_instantiated(self):
self.assertEqual(self.model.state, "new")
assert self.model.state == "new"

def test_known_transition_should_succeed(self):
self.assertTrue(can_proceed(self.model.publish))
assert can_proceed(self.model.publish)
self.model.publish()
self.assertEqual(self.model.state, "published")
assert self.model.state == "published"

self.assertTrue(can_proceed(self.model.hide))
assert can_proceed(self.model.hide)
self.model.hide()
self.assertEqual(self.model.state, "hidden")
assert self.model.state == "hidden"

def test_unknown_transition_fails(self):
self.assertFalse(can_proceed(self.model.hide))
self.assertRaises(TransitionNotAllowed, self.model.hide)
assert not can_proceed(self.model.hide)
with pytest.raises(TransitionNotAllowed):
self.model.hide()

def test_state_non_changed_after_fail(self):
self.assertTrue(can_proceed(self.model.remove))
self.assertRaises(Exception, self.model.remove)
self.assertEqual(self.model.state, "new")
assert can_proceed(self.model.remove)
with pytest.raises(Exception, match="Upss"):
self.model.remove()
assert self.model.state == "new"

def test_allowed_null_transition_should_succeed(self):
self.model.publish()
self.model.notify_all()
self.assertEqual(self.model.state, "published")
assert self.model.state == "published"

def test_unknown_null_transition_should_fail(self):
self.assertRaises(TransitionNotAllowed, self.model.notify_all)
self.assertEqual(self.model.state, "new")
with pytest.raises(TransitionNotAllowed):
self.model.notify_all()
assert self.model.state == "new"

def test_multiple_source_support_path_1_works(self):
self.model.publish()
self.model.steal()
self.assertEqual(self.model.state, "stolen")
assert self.model.state == "stolen"

def test_multiple_source_support_path_2_works(self):
self.model.publish()
self.model.hide()
self.model.steal()
self.assertEqual(self.model.state, "stolen")
assert self.model.state == "stolen"

def test_star_shortcut_succeed(self):
self.assertTrue(can_proceed(self.model.moderate))
assert can_proceed(self.model.moderate)
self.model.moderate()
self.assertEqual(self.model.state, "moderated")
assert self.model.state == "moderated"

def test_plus_shortcut_succeeds_for_other_source(self):
"""Tests that the '+' shortcut succeeds for a source
other than the target.
"""
self.assertTrue(can_proceed(self.model.block))
assert can_proceed(self.model.block)
self.model.block()
self.assertEqual(self.model.state, "blocked")
assert self.model.state == "blocked"

def test_plus_shortcut_fails_for_same_source(self):
"""Tests that the '+' shortcut fails if the source
equals the target.
"""
self.model.block()
self.assertFalse(can_proceed(self.model.block))
self.assertRaises(TransitionNotAllowed, self.model.block)
assert not can_proceed(self.model.block)
with pytest.raises(TransitionNotAllowed):
self.model.block()

def test_empty_string_target(self):
self.model.empty()
self.assertEqual(self.model.state, "")
assert self.model.state == ""


class StateSignalsTests(TestCase):
Expand All @@ -128,22 +133,23 @@ def setUp(self):
post_transition.connect(self.on_post_transition, sender=SimpleBlogPost)

def on_pre_transition(self, sender, instance, name, source, target, **kwargs):
self.assertEqual(instance.state, source)
assert instance.state == source
self.pre_transition_called = True

def on_post_transition(self, sender, instance, name, source, target, **kwargs):
self.assertEqual(instance.state, target)
assert instance.state == target
self.post_transition_called = True

def test_signals_called_on_valid_transition(self):
self.model.publish()
self.assertTrue(self.pre_transition_called)
self.assertTrue(self.post_transition_called)
assert self.pre_transition_called
assert self.post_transition_called

def test_signals_not_called_on_invalid_transition(self):
self.assertRaises(TransitionNotAllowed, self.model.hide)
self.assertFalse(self.pre_transition_called)
self.assertFalse(self.post_transition_called)
with pytest.raises(TransitionNotAllowed):
self.model.hide()
assert not self.pre_transition_called
assert not self.post_transition_called


class TestFieldTransitionsInspect(TestCase):
Expand All @@ -154,8 +160,8 @@ def test_in_operator_for_available_transitions(self):
# store the generator in a list, so we can reuse the generator and do multiple asserts
transitions = list(self.model.get_available_state_transitions())

self.assertIn("publish", transitions)
self.assertNotIn("xyz", transitions)
assert "publish" in transitions
assert "xyz" not in transitions

# inline method for faking the name of the transition
def publish():
Expand All @@ -171,13 +177,13 @@ def publish():
custom="",
)

self.assertTrue(obj in transitions)
assert obj in transitions

def test_available_conditions_from_new(self):
transitions = self.model.get_available_state_transitions()
actual = {(transition.source, transition.target) for transition in transitions}
expected = {("*", "moderated"), ("new", "published"), ("new", "removed"), ("*", ""), ("+", "blocked")}
self.assertEqual(actual, expected)
assert actual == expected

def test_available_conditions_from_published(self):
self.model.publish()
Expand All @@ -191,37 +197,37 @@ def test_available_conditions_from_published(self):
("*", ""),
("+", "blocked"),
}
self.assertEqual(actual, expected)
assert actual == expected

def test_available_conditions_from_hidden(self):
self.model.publish()
self.model.hide()
transitions = self.model.get_available_state_transitions()
actual = {(transition.source, transition.target) for transition in transitions}
expected = {("*", "moderated"), ("hidden", "stolen"), ("*", ""), ("+", "blocked")}
self.assertEqual(actual, expected)
assert actual == expected

def test_available_conditions_from_stolen(self):
self.model.publish()
self.model.steal()
transitions = self.model.get_available_state_transitions()
actual = {(transition.source, transition.target) for transition in transitions}
expected = {("*", "moderated"), ("*", ""), ("+", "blocked")}
self.assertEqual(actual, expected)
assert actual == expected

def test_available_conditions_from_blocked(self):
self.model.block()
transitions = self.model.get_available_state_transitions()
actual = {(transition.source, transition.target) for transition in transitions}
expected = {("*", "moderated"), ("*", "")}
self.assertEqual(actual, expected)
assert actual == expected

def test_available_conditions_from_empty(self):
self.model.empty()
transitions = self.model.get_available_state_transitions()
actual = {(transition.source, transition.target) for transition in transitions}
expected = {("*", "moderated"), ("*", ""), ("+", "blocked")}
self.assertEqual(actual, expected)
assert actual == expected

def test_all_conditions(self):
transitions = self.model.get_all_state_transitions()
Expand All @@ -238,4 +244,4 @@ def test_all_conditions(self):
("*", ""),
("+", "blocked"),
}
self.assertEqual(actual, expected)
assert actual == expected
16 changes: 9 additions & 7 deletions tests/testapp/tests/test_conditions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import pytest
from django.db import models
from django.test import TestCase

Expand Down Expand Up @@ -36,17 +37,18 @@ def setUp(self):
self.model = BlogPostWithConditions()

def test_initial_staet(self):
self.assertEqual(self.model.state, "new")
assert self.model.state == "new"

def test_known_transition_should_succeed(self):
self.assertTrue(can_proceed(self.model.publish))
assert can_proceed(self.model.publish)
self.model.publish()
self.assertEqual(self.model.state, "published")
assert self.model.state == "published"

def test_unmet_condition(self):
self.model.publish()
self.assertEqual(self.model.state, "published")
self.assertFalse(can_proceed(self.model.destroy))
self.assertRaises(TransitionNotAllowed, self.model.destroy)
assert self.model.state == "published"
assert not can_proceed(self.model.destroy)
with pytest.raises(TransitionNotAllowed):
self.model.destroy()

self.assertTrue(can_proceed(self.model.destroy, check_conditions=False))
assert can_proceed(self.model.destroy, check_conditions=False)
12 changes: 6 additions & 6 deletions tests/testapp/tests/test_custom_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def setUp(self):
self.model = BlogPostWithCustomData()

def test_initial_state(self):
self.assertEqual(self.model.state, "new")
assert self.model.state == "new"
transitions = list(self.model.get_available_state_transitions())
self.assertEqual(len(transitions), 1)
self.assertEqual(transitions[0].target, "published")
self.assertDictEqual(transitions[0].custom, {"label": "Publish", "type": "*"})
assert len(transitions) == 1
assert transitions[0].target == "published"
assert transitions[0].custom == {"label": "Publish", "type": "*"}

def test_all_transitions_have_custom_data(self):
transitions = self.model.get_all_state_transitions()
for t in transitions:
self.assertIsNotNone(t.custom["label"])
self.assertIsNotNone(t.custom["type"])
assert t.custom["label"] is not None
assert t.custom["type"] is not None
Loading