Example #1
0
func TestCommandBus(t *testing.T) {
	bus := NewCommandBus()
	if bus == nil {
		t.Fatal("there should be a bus")
	}

	t.Log("handle with no handler")
	command1 := &testutil.TestCommand{eh.NewUUID(), "command1"}
	err := bus.HandleCommand(command1)
	if err != eh.ErrHandlerNotFound {
		t.Error("there should be a ErrHandlerNotFound error:", err)
	}

	t.Log("set handler")
	handler := &TestCommandHandler{}
	err = bus.SetHandler(handler, testutil.TestCommandType)
	if err != nil {
		t.Error("there should be no error:", err)
	}

	t.Log("handle with handler")
	err = bus.HandleCommand(command1)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if handler.command != command1 {
		t.Error("the handled command should be correct:", handler.command)
	}

	err = bus.SetHandler(handler, testutil.TestCommandType)
	if err != eh.ErrHandlerAlreadySet {
		t.Error("there should be a ErrHandlerAlreadySet error:", err)
	}
}
Example #2
0
func TestEventStore(t *testing.T) {
	config := &EventStoreConfig{
		Table:  "eventhorizonTest-" + eh.NewUUID().String(),
		Region: "eu-west-1",
	}
	store, err := NewEventStore(config)
	if err != nil {
		t.Fatal("there should be no error:", err)
	}
	if store == nil {
		t.Fatal("there should be a store")
	}

	t.Log("creating table:", config.Table)
	if err := store.CreateTable(); err != nil {
		t.Fatal("could not create table:", err)
	}

	defer func() {
		t.Log("deleting table:", store.config.Table)
		if err := store.DeleteTable(); err != nil {
			t.Fatal("could not delete table: ", err)
		}
	}()

	// Run the actual test suite.
	testutil.EventStoreCommonTests(t, store)

}
Example #3
0
func TestEventBus(t *testing.T) {
	bus := NewEventBus()
	if bus == nil {
		t.Fatal("there should be a bus")
	}

	observer := testutil.NewMockEventObserver()
	bus.AddObserver(observer)

	t.Log("publish event without handler")
	event1 := &testutil.TestEvent{eh.NewUUID(), "event1"}
	bus.PublishEvent(event1)
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1}) {
		t.Error("the observed events should be correct:", observer.Events)
	}

	t.Log("publish event")
	handler := testutil.NewMockEventHandler("testHandler")
	bus.AddHandler(handler, testutil.TestEventType)
	bus.PublishEvent(event1)
	if !reflect.DeepEqual(handler.Events, []eh.Event{event1}) {
		t.Error("the handler events should be correct:", handler.Events)
	}
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1, event1}) {
		t.Error("the observed events should be correct:", observer.Events)
	}

	t.Log("publish another event")
	bus.AddHandler(handler, testutil.TestEventOtherType)
	event2 := &testutil.TestEventOther{eh.NewUUID(), "event2"}
	bus.PublishEvent(event2)
	if !reflect.DeepEqual(handler.Events, []eh.Event{event1, event2}) {
		t.Error("the handler events should be correct:", handler.Events)
	}
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1, event1, event2}) {
		t.Error("the observed events should be correct:", observer.Events)
	}
}
func TestReadRepository(t *testing.T) {
	// Support Wercker testing with MongoDB.
	host := os.Getenv("MONGO_PORT_27017_TCP_ADDR")
	port := os.Getenv("MONGO_PORT_27017_TCP_PORT")

	url := "localhost"
	if host != "" && port != "" {
		url = host + ":" + port
	}

	repo, err := NewReadRepository(url, "test", "testutil.TestModel")
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if repo == nil {
		t.Error("there should be a repository")
	}

	repo.SetModel(func() interface{} {
		return &testutil.TestModel{}
	})

	defer repo.Close()
	defer func() {
		t.Log("clearing collection")
		if err = repo.Clear(); err != nil {
			t.Fatal("there should be no error:", err)
		}
	}()

	// TODO: Share these tests between implementations.

	t.Log("FindAll with no items")
	result, err := repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 0 {
		t.Error("there should be no items:", len(result))
	}

	t.Log("Save one item")
	model1 := &testutil.TestModel{eh.NewUUID(), "model1", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model1.ID, model1); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err := repo.Find(model1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model1) {
		t.Error("the item should be correct:", model)
	}

	t.Log("Save and overwrite with same ID")
	model1Alt := &testutil.TestModel{model1.ID, "model1Alt", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model1Alt.ID, model1Alt); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err = repo.Find(model1Alt.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model1Alt) {
		t.Error("the item should be correct:", model)
	}

	t.Log("FindAll with one item")
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 1 {
		t.Error("there should be one item:", len(result))
	}
	if !reflect.DeepEqual(result[0], model1Alt) {
		t.Error("the item should be correct:", model)
	}

	t.Log("Save with another ID")
	model2 := &testutil.TestModel{eh.NewUUID(), "model2", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model2.ID, model2); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err = repo.Find(model2.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model2) {
		t.Error("the item should be correct:", model)
	}

	t.Log("FindAll with two items")
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 2 {
		t.Error("there should be two items:", len(result))
	}
	if (!reflect.DeepEqual(result[0], model1Alt) || !reflect.DeepEqual(result[1], model2)) &&
		(!reflect.DeepEqual(result[0], model2) || !reflect.DeepEqual(result[1], model1Alt)) {
		t.Error("the items should be correct:", result)
	}

	t.Log("FindCustom by content")
	result, err = repo.FindCustom(func(c *mgo.Collection) *mgo.Query {
		return c.Find(bson.M{"content": "model1Alt"})
	})
	if len(result) != 1 {
		t.Error("there should be one item:", len(result))
	}
	if !reflect.DeepEqual(result[0], model1Alt) {
		t.Error("the item should be correct:", model)
	}

	t.Log("FindCustom with no query")
	result, err = repo.FindCustom(func(c *mgo.Collection) *mgo.Query {
		return nil
	})
	if err == nil || err != ErrInvalidQuery {
		t.Error("there should be a invalid query error:", err)
	}

	count := 0
	t.Log("FindCustom with query execution in the callback")
	_, err = repo.FindCustom(func(c *mgo.Collection) *mgo.Query {
		if count, err = c.Count(); err != nil {
			t.Error("there should be no error:", err)
		}

		// Be sure to return nil to not execute the query again in FindCustom.
		return nil
	})
	if err == nil || err != ErrInvalidQuery {
		t.Error("there should be a invalid query error:", err)
	}
	if count != 2 {
		t.Error("the count should be correct:", count)
	}

	t.Log("Remove one item")
	err = repo.Remove(model1Alt.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 1 {
		t.Error("there should be one item:", len(result))
	}
	if !reflect.DeepEqual(result[0], model2) {
		t.Error("the item should be correct:", result[0])
	}

	t.Log("Remove non-existing item")
	err = repo.Remove(model1Alt.ID)
	if err != eh.ErrModelNotFound {
		t.Error("there should be a ErrModelNotFound error:", err)
	}
}
Example #5
0
func EventStoreCommonTests(t *testing.T, store eh.EventStore) []eh.Event {
	savedEvents := []eh.Event{}

	t.Log("save no events")
	err := store.Save([]eh.Event{}, 0)
	if err != eh.ErrNoEventsToAppend {
		t.Error("there shoud be a ErrNoEventsToAppend error:", err)
	}

	t.Log("save event, version 1")
	id, _ := eh.ParseUUID("c1138e5f-f6fb-4dd0-8e79-255c6c8d3756")
	event1 := &TestEvent{id, "event1"}
	err = store.Save([]eh.Event{event1}, 0)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	savedEvents = append(savedEvents, event1)

	t.Log("save event, version 2")
	err = store.Save([]eh.Event{event1}, 1)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	savedEvents = append(savedEvents, event1)

	t.Log("save event, version 3")
	event2 := &TestEvent{id, "event2"}
	err = store.Save([]eh.Event{event2}, 2)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	savedEvents = append(savedEvents, event2)

	t.Log("save multiple events, version 4, 5 and 6")
	err = store.Save([]eh.Event{event1, event2, event1}, 3)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	savedEvents = append(savedEvents, event1, event2, event1)

	t.Log("save event for another aggregate")
	id2, _ := eh.ParseUUID("c1138e5e-f6fb-4dd0-8e79-255c6c8d3756")
	event3 := &TestEvent{id2, "event3"}
	err = store.Save([]eh.Event{event3}, 0)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	savedEvents = append(savedEvents, event3)

	t.Log("load events for non-existing aggregate")
	events, err := store.Load(eh.NewUUID())
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(events) != 0 {
		t.Error("there should be no loaded events:", eventsToString(events))
	}

	t.Log("load events")
	events, err = store.Load(id)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(events, []eh.Event{
		event1,                 // Version 1
		event1,                 // Version 2
		event2,                 // Version 3
		event1, event2, event1, // Version 4, 5 and 6
	}) {
		t.Error("the loaded events should be correct:", eventsToString(events))
	}

	t.Log("load events for another aggregate")
	events, err = store.Load(id2)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(events, []eh.Event{event3}) {
		t.Error("the loaded events should be correct:", eventsToString(events))
	}

	return savedEvents
}
Example #6
0
func Example() {
	// Support Wercker testing with MongoDB.
	host := os.Getenv("MONGO_PORT_27017_TCP_ADDR")
	port := os.Getenv("MONGO_PORT_27017_TCP_PORT")

	url := "localhost"
	if host != "" && port != "" {
		url = host + ":" + port
	}

	// Create the event store.
	eventStore, err := eventstore.NewEventStore(url, "demo")
	if err != nil {
		log.Fatalf("could not create event store: %s", err)
	}

	// Create the event bus that distributes events.
	eventBus := eventbus.NewEventBus()
	eventBus.AddObserver(&domain.Logger{})

	// Create the aggregate repository.
	repository, err := eh.NewEventSourcingRepository(eventStore, eventBus)
	if err != nil {
		log.Fatalf("could not create repository: %s", err)
	}

	// Create the aggregate command handler.
	handler, err := eh.NewAggregateCommandHandler(repository)
	if err != nil {
		log.Fatalf("could not create command handler: %s", err)
	}

	// Register the domain aggregates with the dispather. Remember to check for
	// errors here in a real app!
	handler.SetAggregate(domain.InvitationAggregateType, domain.CreateInviteCommand)
	handler.SetAggregate(domain.InvitationAggregateType, domain.AcceptInviteCommand)
	handler.SetAggregate(domain.InvitationAggregateType, domain.DeclineInviteCommand)

	// Create the command bus and register the handler for the commands.
	commandBus := commandbus.NewCommandBus()
	commandBus.SetHandler(handler, domain.CreateInviteCommand)
	commandBus.SetHandler(handler, domain.AcceptInviteCommand)
	commandBus.SetHandler(handler, domain.DeclineInviteCommand)

	// Create and register a read model for individual invitations.
	invitationRepository, err := readrepository.NewReadRepository(url, "demo", "invitations")
	if err != nil {
		log.Fatalf("could not create invitation repository: %s", err)
	}
	invitationRepository.SetModel(func() interface{} { return &domain.Invitation{} })
	invitationProjector := domain.NewInvitationProjector(invitationRepository)
	eventBus.AddHandler(invitationProjector, domain.InviteCreatedEvent)
	eventBus.AddHandler(invitationProjector, domain.InviteAcceptedEvent)
	eventBus.AddHandler(invitationProjector, domain.InviteDeclinedEvent)

	// Create and register a read model for a guest list.
	eventID := eh.NewUUID()
	guestListRepository, err := readrepository.NewReadRepository(url, "demo", "guest_lists")
	if err != nil {
		log.Fatalf("could not create guest list repository: %s", err)
	}
	guestListRepository.SetModel(func() interface{} { return &domain.GuestList{} })
	guestListProjector := domain.NewGuestListProjector(guestListRepository, eventID)
	eventBus.AddHandler(guestListProjector, domain.InviteCreatedEvent)
	eventBus.AddHandler(guestListProjector, domain.InviteAcceptedEvent)
	eventBus.AddHandler(guestListProjector, domain.InviteDeclinedEvent)

	// Clear DB collections.
	eventStore.Clear()
	invitationRepository.Clear()
	guestListRepository.Clear()

	// Issue some invitations and responses.
	// Note that Athena tries to decline the event, but that is not allowed
	// by the domain logic in InvitationAggregate. The result is that she is
	// still accepted.
	athenaID := eh.NewUUID()
	commandBus.HandleCommand(&domain.CreateInvite{InvitationID: athenaID, Name: "Athena", Age: 42})
	commandBus.HandleCommand(&domain.AcceptInvite{InvitationID: athenaID})
	err = commandBus.HandleCommand(&domain.DeclineInvite{InvitationID: athenaID})
	if err != nil {
		log.Printf("error: %s\n", err)
	}

	hadesID := eh.NewUUID()
	commandBus.HandleCommand(&domain.CreateInvite{InvitationID: hadesID, Name: "Hades"})
	commandBus.HandleCommand(&domain.AcceptInvite{InvitationID: hadesID})

	zeusID := eh.NewUUID()
	commandBus.HandleCommand(&domain.CreateInvite{InvitationID: zeusID, Name: "Zeus"})
	commandBus.HandleCommand(&domain.DeclineInvite{InvitationID: zeusID})

	// Read all invites.
	invitations, _ := invitationRepository.FindAll()
	for _, i := range invitations {
		if i, ok := i.(*domain.Invitation); ok {
			log.Printf("invitation: %s - %s\n", i.Name, i.Status)
			fmt.Printf("invitation: %s - %s\n", i.Name, i.Status)
		}
	}

	// Read the guest list.
	l, _ := guestListRepository.Find(eventID)
	if l, ok := l.(*domain.GuestList); ok {
		log.Printf("guest list: %d guests (%d accepted, %d declined)\n",
			l.NumGuests, l.NumAccepted, l.NumDeclined)
		fmt.Printf("guest list: %d guests (%d accepted, %d declined)\n",
			l.NumGuests, l.NumAccepted, l.NumDeclined)
	}

	// records := eventStore.FindAllEventRecords()
	// fmt.Printf("event records:\n")
	// for _, r := range records {
	// 	fmt.Printf("%#v\n", r)
	// }

	// Output:
	// invitation: Athena - accepted
	// invitation: Hades - accepted
	// invitation: Zeus - declined
	// guest list: 3 guests (2 accepted, 1 declined)
}
func TestReadRepository(t *testing.T) {
	repo := NewReadRepository()
	if repo == nil {
		t.Error("there should be a repository")
	}

	// TODO: Share these tests between implementations.

	t.Log("FindAll with no items")
	result, err := repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 0 {
		t.Error("there should be no items:", len(result))
	}

	t.Log("Save one item")
	model1 := &testutil.TestModel{eh.NewUUID(), "model1", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model1.ID, model1); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err := repo.Find(model1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model1) {
		t.Error("the item should be correct:", model)
	}

	t.Log("Save and overwrite with same ID")
	model1Alt := &testutil.TestModel{model1.ID, "model1Alt", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model1Alt.ID, model1Alt); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err = repo.Find(model1Alt.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model1Alt) {
		t.Error("the item should be correct:", model)
	}

	t.Log("FindAll with one item")
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 1 {
		t.Error("there should be one item:", len(result))
	}
	if !reflect.DeepEqual(result[0], model1Alt) {
		t.Error("the item should be correct:", model)
	}

	t.Log("Save with another ID")
	model2 := &testutil.TestModel{eh.NewUUID(), "model2", time.Now().Round(time.Millisecond)}
	if err = repo.Save(model2.ID, model2); err != nil {
		t.Error("there should be no error:", err)
	}
	model, err = repo.Find(model2.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, model2) {
		t.Error("the item should be correct:", model)
	}

	t.Log("FindAll with two items")
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 2 {
		t.Error("there should be two items:", len(result))
	}
	if (!reflect.DeepEqual(result[0], model1Alt) || !reflect.DeepEqual(result[1], model2)) &&
		(!reflect.DeepEqual(result[0], model2) || !reflect.DeepEqual(result[1], model1Alt)) {
		t.Error("the items should be correct:", result)
	}

	t.Log("Remove one item")
	err = repo.Remove(model1Alt.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	result, err = repo.FindAll()
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if len(result) != 1 {
		t.Error("there should be one item:", len(result))
	}
	if !reflect.DeepEqual(result[0], model2) {
		t.Error("the item should be correct:", result[0])
	}

	t.Log("Remove non-existing item")
	err = repo.Remove(model1Alt.ID)
	if err != eh.ErrModelNotFound {
		t.Error("there should be a ErrModelNotFound error:", err)
	}
}
Example #8
0
func main() {
	// Create the event bus that distributes events.
	eventBus := eventhorizon.NewInternalEventBus()
	eventBus.AddGlobalHandler(&LoggerSubscriber{})

	// Create the event store.
	eventStore, err := eventhorizon.NewMongoEventStore(eventBus, "localhost", "demo")
	if err != nil {
		log.Fatalf("could not create event store: %s", err)
	}

	eventStore.RegisterEventType(&InviteCreated{}, func() eventhorizon.Event { return &InviteCreated{} })
	eventStore.RegisterEventType(&InviteAccepted{}, func() eventhorizon.Event { return &InviteAccepted{} })
	eventStore.RegisterEventType(&InviteDeclined{}, func() eventhorizon.Event { return &InviteDeclined{} })

	// Create the aggregate repository.
	repository, err := eventhorizon.NewCallbackRepository(eventStore)
	if err != nil {
		log.Fatalf("could not create repository: %s", err)
	}

	// Register an aggregate factory.
	repository.RegisterAggregate(&InvitationAggregate{},
		func(id eventhorizon.UUID) eventhorizon.Aggregate {
			return &InvitationAggregate{
				AggregateBase: eventhorizon.NewAggregateBase(id),
			}
		},
	)

	// Create the aggregate command handler.
	handler, err := eventhorizon.NewAggregateCommandHandler(repository)
	if err != nil {
		log.Fatalf("could not create command handler: %s", err)
	}

	// Register the domain aggregates with the dispather. Remember to check for
	// errors here in a real app!
	handler.SetAggregate(&InvitationAggregate{}, &CreateInvite{})
	handler.SetAggregate(&InvitationAggregate{}, &AcceptInvite{})
	handler.SetAggregate(&InvitationAggregate{}, &DeclineInvite{})

	// Create the command bus and register the handler for the commands.
	commandBus := eventhorizon.NewInternalCommandBus()
	commandBus.SetHandler(handler, &CreateInvite{})
	commandBus.SetHandler(handler, &AcceptInvite{})
	commandBus.SetHandler(handler, &DeclineInvite{})

	// Create and register a read model for individual invitations.
	invitationRepository, err := eventhorizon.NewMongoReadRepository("localhost", "demo", "invitations")
	if err != nil {
		log.Fatalf("could not create invitation repository: %s", err)
	}
	invitationRepository.SetModel(func() interface{} { return &Invitation{} })
	invitationProjector := NewInvitationProjector(invitationRepository)
	eventBus.AddHandler(invitationProjector, &InviteCreated{})
	eventBus.AddHandler(invitationProjector, &InviteAccepted{})
	eventBus.AddHandler(invitationProjector, &InviteDeclined{})

	// Create and register a read model for a guest list.
	eventID := eventhorizon.NewUUID()
	guestListRepository, err := eventhorizon.NewMongoReadRepository("localhost", "demo", "guest_lists")
	if err != nil {
		log.Fatalf("could not create guest list repository: %s", err)
	}
	guestListRepository.SetModel(func() interface{} { return &GuestList{} })
	guestListProjector := NewGuestListProjector(guestListRepository, eventID)
	eventBus.AddHandler(guestListProjector, &InviteCreated{})
	eventBus.AddHandler(guestListProjector, &InviteAccepted{})
	eventBus.AddHandler(guestListProjector, &InviteDeclined{})

	// Clear DB collections.
	eventStore.Clear()
	invitationRepository.Clear()
	guestListRepository.Clear()

	// Issue some invitations and responses.
	// Note that Athena tries to decline the event, but that is not allowed
	// by the domain logic in InvitationAggregate. The result is that she is
	// still accepted.
	athenaID := eventhorizon.NewUUID()
	commandBus.HandleCommand(&CreateInvite{InvitationID: athenaID, Name: "Athena", Age: 42})
	commandBus.HandleCommand(&AcceptInvite{InvitationID: athenaID})
	err = commandBus.HandleCommand(&DeclineInvite{InvitationID: athenaID})
	if err != nil {
		fmt.Printf("error: %s\n", err)
	}

	hadesID := eventhorizon.NewUUID()
	commandBus.HandleCommand(&CreateInvite{InvitationID: hadesID, Name: "Hades"})
	commandBus.HandleCommand(&AcceptInvite{InvitationID: hadesID})

	zeusID := eventhorizon.NewUUID()
	commandBus.HandleCommand(&CreateInvite{InvitationID: zeusID, Name: "Zeus"})
	commandBus.HandleCommand(&DeclineInvite{InvitationID: zeusID})

	// Read all invites.
	invitations, _ := invitationRepository.FindAll()
	for _, i := range invitations {
		fmt.Printf("invitation: %#v\n", i)
	}

	// Read the guest list.
	guestList, _ := guestListRepository.Find(eventID)
	fmt.Printf("guest list: %#v\n", guestList)

	// records := eventStore.FindAllEventRecords()
	// fmt.Printf("event records:\n")
	// for _, r := range records {
	// 	fmt.Printf("%#v\n", r)
	// }
}
Example #9
0
func TestEventBus(t *testing.T) {
	// Support Wercker testing with MongoDB.
	host := os.Getenv("REDIS_PORT_6379_TCP_ADDR")
	port := os.Getenv("REDIS_PORT_6379_TCP_PORT")

	url := ":6379"
	if host != "" && port != "" {
		url = host + ":" + port
	}

	bus, err := NewEventBus("test", url, "")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}
	if bus == nil {
		t.Fatal("there should be a bus")
	}
	defer bus.Close()
	observer := testutil.NewMockEventObserver()
	bus.AddObserver(observer)

	// Another bus to test the observer.
	bus2, err := NewEventBus("test", url, "")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}
	defer bus2.Close()
	observer2 := testutil.NewMockEventObserver()
	bus2.AddObserver(observer2)

	t.Log("publish event without handler")
	event1 := &testutil.TestEvent{eh.NewUUID(), "event1"}
	bus.PublishEvent(event1)
	<-observer.Recv
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1}) {
		t.Error("the observed events should be correct:", observer.Events)
	}
	<-observer2.Recv
	if !reflect.DeepEqual(observer2.Events, []eh.Event{event1}) {
		t.Error("the second observed events should be correct:", observer2.Events)
	}

	t.Log("publish event")
	handler := testutil.NewMockEventHandler("testHandler")
	bus.AddHandler(handler, testutil.TestEventType)
	bus.PublishEvent(event1)
	if !reflect.DeepEqual(handler.Events, []eh.Event{event1}) {
		t.Error("the handler events should be correct:", handler.Events)
	}
	<-observer.Recv
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1, event1}) {
		t.Error("the observed events should be correct:", observer.Events)
	}
	<-observer2.Recv
	if !reflect.DeepEqual(observer2.Events, []eh.Event{event1, event1}) {
		t.Error("the second observed events should be correct:", observer2.Events)
	}

	t.Log("publish another event")
	bus.AddHandler(handler, testutil.TestEventOtherType)
	event2 := &testutil.TestEventOther{eh.NewUUID(), "event2"}
	bus.PublishEvent(event2)
	if !reflect.DeepEqual(handler.Events, []eh.Event{event1, event2}) {
		t.Error("the handler events should be correct:", handler.Events)
	}
	<-observer.Recv
	if !reflect.DeepEqual(observer.Events, []eh.Event{event1, event1, event2}) {
		t.Error("the observed events should be correct:", observer.Events)
	}
	<-observer2.Recv
	if !reflect.DeepEqual(observer2.Events, []eh.Event{event1, event1, event2}) {
		t.Error("the second observed events should be correct:", observer2.Events)
	}
}