class FeatureTracker_HappyPath_Tests: def setup(self): self.storage = MemStorage("basedir") self.tracker = FeatureTracker(config=default_config(datadir="tracker"), storage=self.storage, warning_listener=WarningRaiser(AssertionError)) def test_initially_contains_no_features(self): assert_that(list(self.tracker.all_features()), has_length(0)) def test_reports_initial_status(self): tracker = FeatureTracker(config=default_config(initial_status="testing"), storage=self.storage, warning_listener=IgnoreWarnings()) assert_that(tracker.initial_status, equal_to("testing")) def test_default_initial_status_is_new(self): assert_that(self.tracker.initial_status, equal_to("new")) def test_can_create_new_features(self): new_feature = self.tracker.create(name="alice", status="pending") assert_that(new_feature.name, equal_to("alice")) assert_that(new_feature.status, equal_to("pending")) def test_new_features_have_default_initial_status_if_status_not_explicitly_specified(self): new_feature = self.tracker.create(name="alice") assert_that(new_feature.status, equal_to(self.tracker.initial_status)) def test_new_features_with_same_status_are_assigned_priority_in_order_of_addition(self): new_a = self.tracker.create(name="new_a", status="new") new_b = self.tracker.create(name="new_b", status="new") pending_a = self.tracker.create(name="pending_a", status="pending") new_c = self.tracker.create(name="new_c", status="new") pending_b = self.tracker.create(name="pending_b", status="pending") assert_that(new_a.priority, equal_to(1)) assert_that(new_b.priority, equal_to(2)) assert_that(new_c.priority, equal_to(3)) assert_that(pending_a.priority, equal_to(1)) assert_that(pending_b.priority, equal_to(2)) def test_can_change_the_priority_of_a_feature(self): alice = self.tracker.create(name="alice") bob = self.tracker.create(name="bob") carol = self.tracker.create(name="carol") dave = self.tracker.create(name="dave") eve = self.tracker.create(name="eve") alice.priority = 3 assert_priority_order("moved first to middle", self.tracker, [bob, carol, alice, dave, eve]) carol.priority = 1 assert_priority_order("moved middle to first", self.tracker, [carol, bob, alice, dave, eve]) eve.priority = 1 assert_priority_order("moved last to first", self.tracker, [eve, carol, bob, alice, dave]) bob.priority = 5 assert_priority_order("moved middle to last", self.tracker, [eve, carol, alice, dave, bob]) eve.priority = 5 assert_priority_order("moved first to last", self.tracker, [carol, alice, dave, bob, eve]) eve.priority = 3 assert_priority_order("moved last to middle", self.tracker, [carol, alice, eve, dave, bob]) dave.priority = 2 assert_priority_order("moved middle to middle", self.tracker, [carol, dave, alice, eve, bob]) def test_can_change_status(self): alice = self.tracker.create(name="alice", status="odd") bob = self.tracker.create(name="bob", status="odd") carol = self.tracker.create(name="carol", status="odd") dave = self.tracker.create(name="dave", status="even") eve = self.tracker.create(name="eve", status="odd") alice.status = "female" assert_status("alice -> female", self.tracker, {"female": [alice], "odd": [bob, carol, eve], "even": [dave]}) bob.status = "male" assert_status("bob -> male", self.tracker, {"female": [alice], "male": [bob], "odd": [carol, eve], "even": [dave]}) carol.status = "female" assert_status("carol -> female", self.tracker, {"female": [alice, carol], "male": [bob], "odd": [eve], "even": [dave]}) dave.status = "male" assert_status("dave -> male", self.tracker, {"female": [alice, carol], "male": [bob, dave], "odd": [eve], "even": []}) eve.status = "female" assert_status("eve -> female", self.tracker, {"female": [alice, carol, eve], "male": [bob, dave], "odd": [], "even": []}) def test_lists_all_statuses_in_alphabetical_order(self): alice = self.tracker.create(name="alice", status="S") bob = self.tracker.create(name="bob", status="U") carol = self.tracker.create(name="carol", status="T") dave = self.tracker.create(name="dave", status="T") eve = self.tracker.create(name="eve", status="S") assert_that(self.tracker.statuses(), equal_to(["S", "T", "U"])) def test_does_not_list_empty_status_indices_by_default(self): alice = self.tracker.create(name="alice", status="S") bob = self.tracker.create(name="bob", status="U") carol = self.tracker.create(name="carol", status="T") dave = self.tracker.create(name="dave", status="T") eve = self.tracker.create(name="eve", status="S") bob.status = "V" # U is now empty assert_that(self.tracker.statuses(), equal_to(["S", "T", "V"])) def test_can_list_empty_status_indices(self): self.test_does_not_list_empty_status_indices_by_default() assert_that(self.tracker.statuses(include_empty=True), equal_to(["S", "T", "U", "V"])) def test_an_unused_status_is_reported_as_empty(self): assert_status("never used status", self.tracker, {"unused_status": []}) def test_lists_names_of_all_features_in_order_of_status_then_priority(self): alice = self.tracker.create(name="alice", status="S") bob = self.tracker.create(name="bob", status="U") carol = self.tracker.create(name="carol", status="T") dave = self.tracker.create(name="dave", status="T") eve = self.tracker.create(name="eve", status="S") assert_that(list(self.tracker.all_features()), equal_to([alice, eve, carol, dave, bob])) def test_lists_names_of_features_with_given_status_in_priority_order(self): alice = self.tracker.create(name="alice", status="same") bob = self.tracker.create(name="bob", status="same") carol = self.tracker.create(name="carol", status="same") dave = self.tracker.create(name="dave", status="same") eve = self.tracker.create(name="eve", status="different") assert_that(list(self.tracker.features_with_status("same")), equal_to([alice, bob, carol, dave])) def test_can_create_feature_with_a_description(self): new_feature = self.tracker.create(name="new-feature", description="the description text") assert_that(new_feature.description, equal_to("the description text")) def test_can_create_feature_with_properties(self): new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"}) assert_that(new_feature.properties, equal_to({"a":"1", "b":"2"})) def test_cannot_create_property_with_reserved_name(self): for name in ["status", "priority", "description"]: try: self.tracker.create(name="feature-for-"+name, properties={name: "y"}) raise AssertionError("should have disallowed property named " + name) except UserError as e: assert_that(str(e), contains_string(name)) def test_cannot_set_property_with_reserved_name(self): feature = self.tracker.create(name="x") for name in ["status", "priority", "description"]: try: feature.properties = {name: "y"} raise AssertionError("should have disallowed property named " + name) except UserError as e: assert_that(str(e), contains_string(name)) def test_if_not_given_a_new_feature_has_an_empty_description(self): new_feature = self.tracker.create(name="new-feature") assert_that(new_feature.description, equal_to("")) def test_can_overwrite_the_description(self): new_feature = self.tracker.create(name="new-feature", description="the initial description") new_feature.description = "a new description" assert_that(new_feature.description, equal_to("a new description")) def test_can_overwrite_the_properties(self): new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"}) new_feature.properties = {"a": "10", "b": "22"} assert_that(new_feature.properties, equal_to({"a": "10", "b": "22"})) def returns_a_copy_of_its_properties(self): new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"}) new_feature.properties["1"] = "10" assert_that(new_feature.properties, equal_to({"a": "1", "b": "2"})) def test_can_report_filename_of_description(self): feature = self.tracker.create(name="bob") assert_that(feature.description_file, equal_to("basedir/tracker/features/bob.description")) def test_can_report_filename_of_properties(self): feature = self.tracker.create(name="carol") assert_that(feature.properties_file, equal_to("basedir/tracker/features/carol.properties.yaml")) def test_description_written_to_storage_immediately_when_feature_is_created(self): alice = self.tracker.create(name="alice", description="first-description") assert_that(self.storage.open("tracker/features/alice.description").read(), equal_to("first-description")) def test_description_written_to_storage_immediately_when_description_is_changed(self): alice = self.tracker.create(name="alice", description="first-description") alice.description = "second-description" assert_that(self.storage.open("tracker/features/alice.description").read(), equal_to("second-description")) def test_can_rename_a_feature(self): f = self.tracker.create(name="alice", description="alice-description", properties={"gender": "female"}) self.tracker.save() original_status = f.status original_priority = f.priority original_description = f.description original_properties = f.properties f.name = "carol" assert_that(f.name, equal_to("carol")) assert_that(f.status, equal_to(original_status)) assert_that(f.priority, equal_to(original_priority)) assert_that(f.description, equal_to(original_description)) assert_that(f.properties, equal_to(original_properties)) try: self.tracker.feature_named("alice") raise AssertionError("should have raised UserError") except UserError: pass assert_that(self.tracker.feature_named("carol"), same_instance(f)) def test_can_rename_a_feature_to_same_name(self): f = self.tracker.create(name="alice", description="alice-description", properties={"gender": "female"}) self.tracker.save() original_status = f.status original_priority = f.priority original_description = f.description original_properties = f.properties f.name = "alice" assert_that(f.name, equal_to("alice")) assert_that(f.status, equal_to(original_status)) assert_that(f.priority, equal_to(original_priority)) assert_that(f.description, equal_to(original_description)) assert_that(f.properties, equal_to(original_properties)) assert_that(self.tracker.feature_named("alice"), same_instance(f)) def test_cannot_rename_a_feature_to_existing_name(self): alice = self.tracker.create(name="alice") bob = self.tracker.create(name="bob") try: bob.name = "alice" raise AssertionError("should have failed") except UserError as expected: pass def test_can_bulk_change_status_of_all_features_with_the_same_status(self): alice = self.tracker.create(name="alice", status="S") bob = self.tracker.create(name="bob", status="S") carol = self.tracker.create(name="carol", status="T") dave = self.tracker.create(name="dave", status="U") self.tracker.bulk_change_status(from_status="S", to_status="T") assert_status("after bulk change", self.tracker, {"S": [], "T": [carol, alice, bob], "U": [dave]}) def test_can_bulk_change_to_same_status(self): alice = self.tracker.create(name="alice", status="S") bob = self.tracker.create(name="bob", status="S") carol = self.tracker.create(name="carol", status="T") dave = self.tracker.create(name="dave", status="U") self.tracker.bulk_change_status(from_status="S", to_status="S") assert_status("after bulk change", self.tracker, {"S": [alice, bob], "T": [carol], "U": [dave]}) def test_purging_a_feature_removes_all_trace_of_it_from_tracker_and_storage(self): alice = self.tracker.create(name="alice", status="new", description="alice-description") alice.properties = {'a': 'some value'} self.tracker.save() assert_that(list(self.storage.list("tracker/features/alice.*")), is_not(equal_to([]))) self.tracker.purge("alice") try: self.tracker.feature_named("alice") raise AssertionError("should have thrown UserError") except UserError as expected: pass self.tracker.save() assert_that(list(self.storage.list("tracker/alice.*")), equal_to([])) def test_deletes_index_file_when_it_becomes_empty(self): alice = self.tracker.create(name="alice", status="testing") bob = self.tracker.create(name="bob", status="testing") carol = self.tracker.create(name="carol", status="testing") assert_that(self.storage.exists("tracker/status/testing.index"), "index file exists") alice.status = "another-status" bob.status = "another-status" carol.status = "another-status" assert_that(not self.storage.exists("tracker/status/testing.index"), "index file should not exist after all entries have been removed") def test_deletes_index_file_when_it_becomes_by_purging_features(self): self.tracker.create(name="alice", status="testing") self.tracker.create(name="bob", status="testing") assert_that(self.storage.exists("tracker/status/testing.index"), "index file exists") self.tracker.purge("alice") self.tracker.purge("bob") assert_that(not self.storage.exists("tracker/status/testing.index"), "index file should not exist after all entries have been removed") def test_asking_for_an_empty_index_does_not_create_an_empty_file(self): empty = self.tracker.features_with_status("testing") assert_that(empty, equal_to([])) assert_that(not self.storage.exists("tracker/status/testing.index"), "should not have created an empty index file")