Browse Source

Setup to be able to run the first test

main
Bèr Kessels 1 year ago
parent
commit
8d260a1f14

+ 2
- 2
Rakefile View File

@@ -11,7 +11,7 @@ task :database do
end

desc 'Run Event Stream Processors'
task run_processors: [:environment, :database] do
task run_processors: %i[environment database] do
puts 'Starting Event Stream processors'

event_source = Roost.event_source
@@ -56,7 +56,7 @@ namespace :db do
end

desc 'Migrate database'
task migrate: [:environment, :database] do
task migrate: %i[environment database] do
database = EventSourcery::Postgres.config.event_store_database
begin
EventSourcery::Postgres::Schema.create_event_store(db: database)

+ 29
- 0
app/aggregates/member.rb View File

@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Roost
module Aggregates
##
# A +Member+ is a registered, available account on an +Instance+.
# This can be a human, bot, or other actor. It can login, has a profile
# and can interact with other members on this and other +Instances+
class Member
include EventSourcery::AggregateRoot

apply MemberAdded do |event|
# Mutate state of aggregate based on event, e.g.
# @member_addmemberred_occurred = true
end

def add_member(payload)
# Perform any relevant contextual validations on aggregate

# Apply the event without persistence
apply_event(
MemberAdded,
aggregate_id: id,
body: payload
)
end
end
end
end

+ 46
- 0
app/commands/member/add_member.rb View File

@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'app/aggregates/member'

module Roost
module Commands
module Member
module AddMember
##
# Command to add a new +Member+
class Command
attr_reader :aggregate_id, :payload

def initialize(params)
@aggregate_id = params.delete(:aggregate_id)
@payload = params # Select the parameters you want to allow
end

def validate
# Add validation here
end
end

##
# CommandHandler for +AddMember+ Commands
class CommandHandler
def initialize(repository: Roost.repository)
@repository = repository
end

def handle(command)
command.validate

aggregate = repository.load(Aggregates::Member, command.aggregate_id)
aggregate.add_member(command.payload)
repository.save(aggregate)
end

private

attr_reader :repository
end
end
end
end
end

+ 3
- 0
app/events/member_added.rb View File

@@ -0,0 +1,3 @@
# frozen_string_literal: true

MemberAdded = Class.new(EventSourcery::Event)

+ 21
- 0
app/projections/members/projector.rb View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Roost
module Projections
module Members
##
# Stores Members in their distinct query table
class Projector
include EventSourcery::Postgres::Projector

projector_name :members

table :members do
column :member_id, 'UUID NOT NULL'
column :name, :text
column :email, :text
end
end
end
end
end

+ 2
- 0
config/database.rb View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true

EventSourcery::Postgres.configure do |config|
database = Sequel.connect(Roost.config.database_url)


+ 44
- 0
test/integration/member_invites_member_test.rb View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'test_helper'
require 'support/workflows/add_member'

describe 'member invites member' do
describe 'POST /invitations' do
let(:invitee_email) { 'irene@example.com' }
let(:invitee_name) { 'Irene' }

before do
Workflows::AddMember.new(self).call
header('Content-Type', 'application/vnd.api+json')
header('Accept', 'application/vnd.api+json')
end

it 'sends an invitation email' do
skip 'Implement mailer'
mailer = Roost.config.mailer
assert_equal mailer.sent.length, 0
post_json(
'/invitations',
{
data: {
type: 'invitation',
attributes: {
to_email: invitee_email,
to_name: invitee_name
}
}
}.to_json
)
assert_status(200)
assert_match(%r{http://example\.org/invitations/(.*)},
last_response.headers['Location'])

invitation_email = mailer.sent.last
assert_equal?(invitation_email.to,
"\"#{invitee_name}\" <#{invitee_email}>")
assert_equal(invitee_email.from,
"\"#{invitee_name}\" <#{invitee_email}>")
end
end
end

+ 33
- 0
test/support/event_helpers.rb View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true

##
# Helpers for testing events.
module EventHelpers
def last_event(aggregate_id = nil)
unless aggregate_id
event_id = Roost.event_store.latest_event_id
aggregate_id = Roost.event_store.get_next_from(event_id).last.aggregate_id
end

Hours.event_store.get_events_for_aggregate_id(aggregate_id).last
end

def projector_process_event(aggregate_id)
projectors.each do |projector|
event = last_event(aggregate_id)
projector.process(event)
end
end

def setup_projectors
projectors.each(&:setup)
end

protected

def projectors
@projectors = [
Roost::Projections::Invitations::Projector.new
]
end
end

+ 11
- 0
test/support/file_helpers.rb View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true

##
# Helpers for testing against files
module FileHelpers
def assert_file_contains(file, string)
contents = File.read(file)
message = %(Expected file "#{file}" to contain "#{string}")
assert_includes(contents, string, message)
end
end

+ 41
- 0
test/support/time_helpers.rb View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true

module TimeHelpers
##
# Time Helper to freeze the time at die_wende for the duration of a block
# stub goes away once the block is done
def at_time(time = die_wende, &block)
Time.stub :now, time do
Date.stub :today, time.to_date do
yield block
end
end
end

##
# Time Helper, returns a Time
# Time is the moment the Berlin Wall fell: 9-11-1989 18:57:00
#
# November 1989
# zo ma di wo do vr za
# 1 2 3 4
# > 5 6 7 8 9 10 11
# 12 13 14 15 16 17 18
# 19 20 21 22 23 24 25
# 26 27 28 29 30
def die_wende
Time.local(1989, 11, 9, 18, 57, 0, 0)
end

##
# Assert after a certain time
def assert_time_after(expected, actual = Time.now.getlocal)
assert expected < actual, "#{actual} is not after #{expected}"
end

##
# Assert before a certain time
def assert_time_before(expected, actual = Time.now.getlocal)
assert expected > actual, "#{actual} is not before #{expected}"
end
end

+ 46
- 0
test/support/workflows/add_member.rb View File

@@ -0,0 +1,46 @@
# frozen_string_literal: true

module Workflows
class AddMember
attr_reader :test_obj

def initialize(test_obj)
@test_obj = test_obj
end

def call
create_events
process_events
end

def create_events
members.each do |member_attributes|
command = Roost::Commands::Member::AddMember::Command.new(member_attributes)
Roost::Commands::Member::AddMember::CommandHandler.new.handle(command)
end
end

def process_events
Roost.event_source.each_by_range(0, 1) do |event|
esps.each { |ep| ep.process(event) }
end
end

private

## TODO call test_obj.class.method_defined?(:members) instead.
def members
[
{
aggregate_id: SecureRandom.uuid,
name: 'Harry Potter',
email: 'harry@example.com'
}
]
end

def esps
@esps ||= [Roost::Projections::Members::Projector.new]
end
end
end

Loading…
Cancel
Save