Ejemplo n.º 1
0
        public void Load_with_missing_due_card()
        {
            var es = new InMemoryEventstore();

            es.Record(new Event[] {
                new NewCardEncountered {
                    Question = "q3", Answer = "a3", Tags = "t3", Id = "3"
                },
                new CardMovedTo {
                    CardId = "3", BinIndex = 3
                },
                new DueCardSelected {
                    CardId = "3"
                },

                new CardFoundMissing {
                    CardId = "3"
                },
            });
            var sut = new DueCardContextManager(es);

            var result = sut.Load(new DueCardQuery()).Ctx as DueCardContextModel;

            result.DueCard.Should().BeNull();
        }
Ejemplo n.º 2
0
        public void Load_with_empty_bin()
        {
            var es  = new InMemoryEventstore();
            var sut = new SelectDueCardContextManager(es);

            es.Record(new Event[] {
                new CardMovedTo {
                    CardId = "1", BinIndex = 1
                },
                new CardMovedTo {
                    CardId = "3", BinIndex = 3
                },
            });

            var result = sut.Load(new SelectDueCardCommand()).Ctx as SelectDueCardContextModel;

            result.Should().BeEquivalentTo(new SelectDueCardContextModel {
                Bins = new[] {
                    new string[0],
                    new[] { "1" },
                    new string[0],
                    new[] { "3" }
                },

                DueBinIndex = -1,

                Config = null
            });
        }
Ejemplo n.º 3
0
        public void Load()
        {
            var es = new InMemoryEventstore();

            es.Record(new Event[] {
                new CardMovedTo {
                    CardId = "1", BinIndex = 0
                },
                new CardMovedTo {
                    CardId = "x", BinIndex = 1
                },
                new CardMovedTo {
                    CardId = "2", BinIndex = 2
                },
                new CardMovedTo {
                    CardId = "1", BinIndex = 1
                },
                new CardFoundMissing {
                    CardId = "x"
                }
            });

            var sut = new RegisterAnswerContextManager(es);

            var result = sut.Load(new RegisterAnswerCommand()).Ctx as RegisterAnswerContextModel;

            result.CardsInBins.Should().BeEquivalentTo(new Dictionary <string, int> {
                { "1", 1 },
                { "2", 2 }
            });
        }
Ejemplo n.º 4
0
        public void Load_with_no_due_card_because_it_was_moved()
        {
            var es = new InMemoryEventstore();

            es.Record(new Event[] {
                new NewCardEncountered {
                    Question = "q1", Answer = "a1", Tags = "t1", Id = "1"
                },
                new CardMovedTo {
                    CardId = "1", BinIndex = 1
                },
                new DueCardSelected {
                    CardId = "1"
                },

                new CardMovedTo {
                    CardId = "1", BinIndex = 2
                },
            });
            var sut = new DueCardContextManager(es);

            var result = sut.Load(new DueCardQuery()).Ctx as DueCardContextModel;

            result.DueCard.Should().BeNull();
        }
Ejemplo n.º 5
0
        public void Load()
        {
            var es = new InMemoryEventstore();

            es.Record(new Event[] {
                new NewCardEncountered {
                    Question = "q1", Answer = "a1", Tags = "t1", Id = "1"
                },
                new CardMovedTo {
                    CardId = "1", BinIndex = 1
                },
                new DueCardSelected {
                    CardId = "1"
                },

                new NewCardEncountered {
                    Question = "q2", Answer = "a2", Tags = "t2", Id = "2"
                },
                new CardMovedTo {
                    CardId = "2", BinIndex = 2
                },
                new DueCardSelected {
                    CardId = "2"
                },

                new NewCardEncountered {
                    Question = "q3", Answer = "a3", Tags = "t3", Id = "3"
                },
                new CardMovedTo {
                    CardId = "3", BinIndex = 3
                },
                new DueCardSelected {
                    CardId = "3"
                },

                new CardFoundMissing {
                    CardId = "3"
                },

                new DueCardSelected {
                    CardId = "2"
                },

                new CardWasChanged {
                    CardId = "2", Question = "q2v2", Answer = "a2v2", Tags = "t2v2"
                },
            });
            var sut = new DueCardContextManager(es);

            var result = sut.Load(new DueCardQuery()).Ctx as DueCardContextModel;

            result.DueCard.Should().BeEquivalentTo(new DueCardFoundQueryResult {
                CardId   = "2",
                Question = "q2v2",
                Answer   = "a2v2",
                Tags     = "t2v2",
                BinIndex = 2
            });
        }
Ejemplo n.º 6
0
        public static void Main(string[] args)
        {
            var es = new InMemoryEventstore();

            es.Record(new A());
            es.Record(new C());
            es.Record(new B());

            nsimpleeventstore.adapters.EventArchive.Write("myarchive.json", es.Replay().Events);

            var events = EventArchive.Read("myarchive.json");

            var es2 = new InMemoryEventstore(events);

            Console.WriteLine(es2.Replay().Events.Length);
        }
Ejemplo n.º 7
0
        public void InMemoryEventstore_acceptance_test()
        {
            using (var sut = new InMemoryEventstore())
            {
                Event e0 = new TodoAdded("do dishes");
                sut.Record(e0);
                sut.Record(new TodoAdded("walk dog"));

                Event e2 = new TodoAdded("write report");
                sut.Record(new Event[] { e2, new TodoCategorized(e2.Id, "work") });
                sut.Record(new TodoDone(e0.Id));

                var result = sut.Replay();
                var todos  = result.Events.Aggregate(new Dictionary <string, ToDoItem>(), Map);

                Assert.Equal(2, todos.Count);
                Assert.Equal("write report", todos[e2.Id].Subject);
                Assert.Contains("work", todos[e2.Id].Categories);


                Dictionary <string, ToDoItem> Map(Dictionary <string, ToDoItem> items, Event e)
                {
                    switch (e)
                    {
                    case TodoAdded a:
                        items[a.Id] = new ToDoItem {
                            Id = a.Id, Subject = a.Subject
                        };
                        break;

                    case TodoDone d:
                        items.Remove(d.EntityId);
                        break;

                    case TodoCategorized c:
                        foreach (var cat in c.Categories)
                        {
                            items[c.EntityId].Categories.Add(cat);
                        }
                        break;
                    }

                    return(items);
                }
            }
        }
        public void Initialize_record_and_replay()
        {
            var sut = new InMemoryEventstore(new[]
            {
                new TestEvent {
                    Text = "foo"
                },
                new TestEvent {
                    Text = "bar"
                }
            });

            sut.Record(new TestEvent {
                Text = "baz"
            });

            var result = sut.Replay().Events.Select(e => (e as TestEvent).Text);

            Assert.Equal(new[] { "foo", "bar", "baz" }, result);
        }
Ejemplo n.º 9
0
        public void Load_without_cards()
        {
            var es  = new InMemoryEventstore();
            var sut = new SelectDueCardContextManager(es);

            es.Record(new Event[] {
            });

            var result = sut.Load(new SelectDueCardCommand()).Ctx as SelectDueCardContextModel;

            result.Should().BeEquivalentTo(new SelectDueCardContextModel {
                Bins = new[] {
                    new string[0], // bin 0 and
                    new string[0]  // bin 1 always get created
                },

                DueBinIndex = -1,

                Config = null
            });
        }
Ejemplo n.º 10
0
        public void Load_message_context()
        {
            var es = new InMemoryEventstore();

            es.Record(new Event[]
            {
                new BoxConfigured {
                    Bins = new[] { new BoxConfigured.Bin {
                                       LowerDueThreshold = 1, UpperDueThreshold = 2
                                   } }
                },

                new NewCardEncountered {
                    Question = "q1", Answer = "a1", Tags = "t1", Id = "1"
                },
                new CardMovedTo {
                    CardId = "1", BinIndex = 0
                },
                new NewCardEncountered {
                    Question = "q2", Answer = "a2", Tags = "t2,t3", Id = "2"
                },
                new CardMovedTo {
                    CardId = "2", BinIndex = 3
                },
                new NewCardEncountered {
                    Question = "q3", Answer = "a3", Tags = "", Id = "3"
                },
                new CardWasChanged {
                    Question = "q1v2", Answer = "a1v2", Tags = "t1", CardId = "1"
                },
                new CardMovedTo {
                    CardId = "1", BinIndex = 2
                },
                new CardFoundMissing {
                    CardId = "3"
                },

                new BoxConfigured {
                    Bins = new[] { new BoxConfigured.Bin {
                                       LowerDueThreshold = 10, UpperDueThreshold = 20
                                   } }
                }
            });
            var sut = new SyncContextManagement(es);


            var result = sut.Load(new SyncCommand());


            var ctxModel = result.Ctx as SyncContextModel;

            ctxModel.Config.Should().BeEquivalentTo(new FlashcardboxConfig {
                Bins = new[] { new FlashcardboxConfig.Bin {
                                   LowerDueThreshold = 10,
                                   UpperDueThreshold = 20
                               } }
            });

            ctxModel.Flashcards.Should().BeEquivalentTo(new Dictionary <string, (string binIndex, string hash)> {
                { "1", ("2", FlashcardHash.Calculate("q1v2", "a1v2", "t1")) },
                { "2", ("3", FlashcardHash.Calculate("q2", "a2", "t2,t3")) }
            });
Ejemplo n.º 11
0
        public void Load()
        {
            var es  = new InMemoryEventstore();
            var sut = new SelectDueCardContextManager(es);

            es.Record(new Event[] {
                new BoxConfigured {
                    Bins = new[] {
                        new BoxConfigured.Bin {
                            LowerDueThreshold = 10,
                            UpperDueThreshold = 20
                        },
                    }
                },

                new CardMovedTo {
                    CardId = "1", BinIndex = 1
                },
                new CardMovedTo {
                    CardId = "2.1", BinIndex = 2
                },
                new CardMovedTo {
                    CardId = "2.2", BinIndex = 1
                },
                new DueCardSelected {
                    CardId = "2.2", BinIndex = 1
                },

                new CardMovedTo {
                    CardId = "3.1", BinIndex = 3
                },
                new CardMovedTo {
                    CardId = "3.2", BinIndex = 3
                },
                new CardMovedTo {
                    CardId = "3.3", BinIndex = 1
                },
                new CardMovedTo {
                    CardId = "3.x", BinIndex = 3
                },

                new CardMovedTo {
                    CardId = "2.2", BinIndex = 2
                },

                new CardFoundMissing {
                    CardId = "3.x"
                },
                new CardMovedTo {
                    CardId = "3.3", BinIndex = 3
                },
                new DueCardSelected {
                    CardId = "3.3", BinIndex = 3
                }
            });

            var result = sut.Load(new SelectDueCardCommand()).Ctx as SelectDueCardContextModel;

            result.Should().BeEquivalentTo(new SelectDueCardContextModel {
                Bins = new[] {
                    new string[0],
                    new[] { "1" },
                    new[] { "2.1", "2.2" },
                    new[] { "3.1", "3.2", "3.3" }
                },

                DueBinIndex = 3,

                Config = new FlashcardboxConfig {
                    Bins = new [] {
                        new FlashcardboxConfig.Bin {
                            LowerDueThreshold = 10,
                            UpperDueThreshold = 20
                        }
                    }
                }
            });
        }
Ejemplo n.º 12
0
        public void Some_cards_are_not_memorized_right_away()
        {
            const string DBPATH = "sampledb_acceptance";

            File.Copy(Path.Combine(DBPATH, "flashcards v1.csv"), Path.Combine(DBPATH, "flashcards.csv"), true);

            var db = new FlashcardboxDb(DBPATH);
            var es = new InMemoryEventstore();
            var mh = new MessageHandling(es, db);


            // box initialized
            var status = mh.Handle(new SyncCommand());

            // 0:a12bcde3fg,1:,2:,3:
            Memorized("a"); //0:cde3fg,1:12b*,2:a,3:
            Memorized("1"); //0:cde3fg,1:2b*,2:a1,3:
            Forgotten("2"); //0:cde3fg,1:b2*,2:a1,3:
            Memorized("b"); //0:cde3fg,1:2*,2:a1b,3:
            Memorized("2"); //0:cde3fg,1:2*,2:a1b,3: -> 0:3fg,1:2cde*,2:a1b,3: -> 0:3fg,1:cde*,2:a1b2,3:
            Forgotten("c"); //0:3fg,1:dec*,2:a1b2,3:
            Memorized("d"); //0:3fg,1:ec*,2:a1b2d,3:
            Memorized("e"); //0:3fg,1:c*,2:a1b2de,3:
            Memorized("a"); //0:3fg,1:c*,2:1b2de*,3:a
            Forgotten("1"); //0:3fg,1:c1,2:b2de*,3:a
            Forgotten("b"); //0:3fg,1:c1b,2:2de*,3:a
            Memorized("2"); //0:3fg,1:c1b,2:de*,3:a2
            Memorized("c"); //0:3fg,1:c1b,2:de*,3:a2 -> 0:fg,1:c1b3,2:de*,3:a2 -> 0:fg,1:1b3*,2:dec,3:a2
            Memorized("1"); //0:fg,1:b3*,2:dec1,3:a2
            Memorized("b"); //0:fg,1:3*,2:dec1b,3:a2
            Forgotten("d"); //0:fg,1:3d,2:ec1b*,3:a2
            Memorized("e"); //0:fg,1:3d,2:c1b*,3:a2e
            Memorized("c"); //0:fg,1:3d,2:1b*,3:a2ec
            Memorized("3"); //0:,1:dfg*,2:1b3,3:a2ec
            Memorized("d"); //0:,1:fg*,2:1b3d,3:a2ec
            Memorized("f"); //0:,1:g*,2:1b3df,3:a2ec
            Forgotten("1"); //0:,1:g1,2:b3df*,3:a2ec
            Memorized("b"); //0:,1:g1,2:3df*,3:a2ecb
            Memorized("3"); //0:,1:g1,2:df*,3:a2ecb3
            Memorized("g"); //0:,1:1*,2:dfg,3:a2ecb3
            Memorized("1"); //0:,1:*,2:dfg1,3:a2ecb3
            Memorized("d"); //0:,1:,2:fg1*,3:a2ecb3d
            Forgotten("f"); //0:,1:f,2:g1*,3:a2ecb3d
            Memorized("f"); //0:,1:*,2:g1f*,3:a2ecb3d
            Memorized("g"); //0:,1:*,2:1f*,3:a2ecb3dg
            Memorized("1"); //0:,1:*,2:f*,3:a2ecb3dg1
            Memorized("f"); //0:,1:*,2:*,3:a2ecb3dg1f

            mh.Handle(new SelectDueCardCommand()).Should().BeOfType <Failure>();


            void Memorized(string question)
            {
                mh.Handle(new SelectDueCardCommand());
                var card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;

                mh.Handle(new RegisterAnswerCommand {
                    CardId = card.CardId, CorrectlyAnswered = true
                });
                card.Question.Should().Be(question);
            }

            void Forgotten(string question)
            {
                mh.Handle(new SelectDueCardCommand());
                var card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;

                mh.Handle(new RegisterAnswerCommand {
                    CardId = card.CardId, CorrectlyAnswered = false
                });
                card.Question.Should().Be(question);
            }
        }
Ejemplo n.º 13
0
        public void Cards_only_move_forward()
        {
            const string DBPATH = "sampledb_acceptance";

            File.Copy(Path.Combine(DBPATH, "flashcards v1.csv"), Path.Combine(DBPATH, "flashcards.csv"), true);

            var db = new FlashcardboxDb(DBPATH);
            var es = new InMemoryEventstore();
            var mh = new MessageHandling(es, db);

            // box not yet initialized
            var progress = mh.Handle(new ProgressQuery());

            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin(),
                    new ProgressQueryResult.Bin()
                }
            });

            // box initialized
            var status = mh.Handle(new SyncCommand());

            status.Should().BeOfType <SyncSuccess>();
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 10
                    },
                    new ProgressQueryResult.Bin {
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4
                    },
                    new ProgressQueryResult.Bin {
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // learn first card
            status = mh.Handle(new SelectDueCardCommand());
            status.Should().BeOfType <Success>();
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 6
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 4,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            var card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;

            card.Question.Should().Be("a");

            status = mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });
            status.Should().BeOfType <Success>();
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 6
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 3,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 1,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // learn second card
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("1");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 6
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 2,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 2,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // learn card 3
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("2");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 6
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 1,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 3,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // learning card 4 first leads to a refill of bin 1
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("b");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });
            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 3
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 3,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 4,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // learning card 5 fills up bin 2...
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("c");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });

            // ...but bin 1 stays due because its lower threshold hasn't been reached
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("d");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });

            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 3
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 1,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 6,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5
                    },
                    new ProgressQueryResult.Bin()
                }
            });

            // this only changes now!
            mh.Handle(new SelectDueCardCommand());
            card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
            card.Question.Should().Be("a");
            mh.Handle(new RegisterAnswerCommand {
                CardId = card.CardId, CorrectlyAnswered = true
            });

            progress = mh.Handle(new ProgressQuery());
            progress.Should().BeEquivalentTo(new ProgressQueryResult {
                Bins = new[] {
                    new ProgressQueryResult.Bin {
                        Count = 3
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 1,
                        LowerDueThreshold = 2,
                        UpperDueThreshold = 4,
                    },
                    new ProgressQueryResult.Bin {
                        Count             = 5,
                        LowerDueThreshold = 3,
                        UpperDueThreshold = 5,
                        IsDue             = true
                    },
                    new ProgressQueryResult.Bin {
                        Count = 1
                    }
                }
            });

            // learn all cards:
            // 1(3,1,4,2), 2(3,1,3,3), b(3,1,2,4), e(0,3,4,3), 3(0,2,5,3), f(0,1-g,5-cde3f,4-a12b)
            // c(0,1-g,5-de3f,4-a12bc), d(0,1-g,3-e3f,5-a12bcd), e(0,1-g,2-3f,6-a12bcde)
            // g(0,0,3-3fg,7-a12bcde), 3(0,0,2-fg,8-a12bcde3), f(0,0,1-g,9-a12bcde3f), g(0,0,0,10-a12bcde3fg)

            foreach (var q in "12be3fcdeg3fg".ToCharArray())
            {
                mh.Handle(new SelectDueCardCommand());
                card = mh.Handle(new DueCardQuery()) as DueCardFoundQueryResult;
                mh.Handle(new RegisterAnswerCommand {
                    CardId = card.CardId, CorrectlyAnswered = true
                });
                card.Question.Should().Be(q.ToString());
            }

            mh.Handle(new SelectDueCardCommand()).Should().BeOfType <Failure>();
        }
Ejemplo n.º 14
0
        public void Fluent()
        {
            var es  = new InMemoryEventstore();
            var sut = new MessagePump(es);


            sut.On <AddTodoCmd>().Use(new AddTodoCmdCtxModelManager()).Do(new AddTodoCmdProcessor());
            sut.On <AllTodosQuery>().Use(new AllTodosQueryCtxModelManager()).Do(new AllTodosQueryProcessor());
            var checkToDoCtxModelManager = new CheckTodoCmdCtxModelManager();

            sut.On <CheckTodoCmd>().Load(checkToDoCtxModelManager).Finally(checkToDoCtxModelManager).Do(new CheckTodoCmdProcessor());


            // add a couple of tasks
            var response = sut.Handle(new AddTodoCmd {
                Subject = "foo"
            });

            Assert.IsType <Success>(response.Msg);
            Assert.Empty(response.Notifications);

            response = sut.Handle(new AddTodoCmd {
                Subject = "bar"
            });
            Assert.IsType <Success>(response.Msg);
            Assert.Empty(response.Notifications);

            response = sut.Handle(new AddTodoCmd {
                Subject = "baz"
            });
            Assert.IsType <Success>(response.Msg);
            Assert.Empty(response.Notifications);

            // what are the currently active tasks?
            response = sut.Handle(new AllTodosQuery());
            Assert.Empty(response.Notifications);
            var result = response.Msg as AllTodosQueryResult;

            Assert.Equal(new[] { "foo", "bar", "baz" }, result.Todos.Select(x => x.Description));

            // check a task as done
            response = sut.Handle(new CheckTodoCmd {
                Id = result.Todos[0].Id
            });
            Assert.IsType <Success>(response.Msg);
            Assert.Empty(response.Notifications);

            // ...and it still gets listed
            response = sut.Handle(new AllTodosQuery());
            Assert.Empty(response.Notifications);
            result = response.Msg as AllTodosQueryResult;
            Assert.Equal(new[] { "foo, done", "bar", "baz" }, result.Todos.Select(x => x.Description));

            // ...unless done tasks are explicitly excluded
            response = sut.Handle(new AllTodosQuery {
                ActiveOnly = true
            });
            Assert.Empty(response.Notifications);
            result = response.Msg as AllTodosQueryResult;
            Assert.Equal(new[] { "bar", "baz" }, result.Todos.Select(x => x.Description));

            // check all remaining tasks - and a notification gets created, once all are done
            sut.Handle(new CheckTodoCmd {
                Id = result.Todos[0].Id
            });
            response = sut.Handle(new CheckTodoCmd {
                Id = result.Todos[1].Id
            });
            Assert.IsType <Success>(response.Msg);
            Assert.Single(response.Notifications);
            Assert.IsType <FreeCapacityAvailableNotification>(response.Notifications[0]);
        }