startback 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/startback/audit/trailer.rb +145 -0
- data/lib/startback/audit.rb +1 -0
- data/lib/startback/bus/bunny/async.rb +117 -0
- data/lib/startback/bus/bunny.rb +1 -0
- data/lib/startback/bus/memory/async.rb +40 -0
- data/lib/startback/bus/memory/sync.rb +30 -0
- data/lib/startback/bus/memory.rb +2 -0
- data/lib/startback/bus.rb +94 -0
- data/lib/startback/caching/entity_cache.rb +80 -0
- data/lib/startback/caching/store.rb +34 -0
- data/lib/startback/context/middleware.rb +1 -1
- data/lib/startback/context.rb +93 -4
- data/lib/startback/event.rb +43 -0
- data/lib/startback/operation.rb +39 -6
- data/lib/startback/support/fake_logger.rb +18 -0
- data/lib/startback/support/hooks.rb +48 -0
- data/lib/startback/support/operation_runner.rb +150 -0
- data/lib/startback/support/robustness.rb +153 -0
- data/lib/startback/support.rb +3 -0
- data/lib/startback/version.rb +2 -2
- data/lib/startback/web/api.rb +3 -4
- data/lib/startback/web/catch_all.rb +12 -5
- data/lib/startback/web/middleware.rb +13 -0
- data/lib/startback.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/audit/test_trailer.rb +88 -0
- data/spec/unit/bus/memory/test_async.rb +41 -0
- data/spec/unit/bus/memory/test_sync.rb +41 -0
- data/spec/unit/caching/test_entity_cache.rb +109 -0
- data/spec/unit/context/test_abstraction_factory.rb +64 -0
- data/spec/unit/support/hooks/test_after_hook.rb +54 -0
- data/spec/unit/support/hooks/test_before_hook.rb +54 -0
- data/spec/unit/support/operation_runner/test_around_run.rb +157 -0
- data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
- data/spec/unit/support/test_robusteness.rb +209 -0
- data/spec/unit/test_context.rb +51 -0
- data/spec/unit/test_event.rb +69 -0
- data/spec/unit/test_operation.rb +0 -3
- metadata +32 -4
@@ -0,0 +1,43 @@
|
|
1
|
+
module Startback
|
2
|
+
#
|
3
|
+
# An Event occuring a given context and having a type and attached data.
|
4
|
+
#
|
5
|
+
# Event instances have String types that are by default unrelated to ruby
|
6
|
+
# classes. Also, this Event class has a `json` information contract that
|
7
|
+
# allows dumping & reloading them easily. A context or context_factory may
|
8
|
+
# be provided in dress world to reload the event context from data, but
|
9
|
+
# that logic is opaque to this class.
|
10
|
+
#
|
11
|
+
# This class is intended to be subclassed if a more specific event protocol
|
12
|
+
# is wanted.
|
13
|
+
#
|
14
|
+
class Event
|
15
|
+
|
16
|
+
def initialize(type, data, context = nil)
|
17
|
+
@type = type.to_s
|
18
|
+
@data = OpenStruct.new(data)
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
attr_reader :context, :type, :data
|
22
|
+
|
23
|
+
def self.json(src, world = {})
|
24
|
+
parsed = JSON.parse(src)
|
25
|
+
context = if world[:context]
|
26
|
+
world[:context]
|
27
|
+
elsif world[:context_factory]
|
28
|
+
world[:context_factory].call(parsed)
|
29
|
+
end
|
30
|
+
Event.new(parsed['type'], parsed['data'], context)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_json(*args, &bl)
|
34
|
+
h = {
|
35
|
+
type: self.type,
|
36
|
+
data: data.to_h
|
37
|
+
}
|
38
|
+
h[:context] = context if context
|
39
|
+
h.to_json(*args, &bl)
|
40
|
+
end
|
41
|
+
|
42
|
+
end # class Event
|
43
|
+
end # module Startback
|
data/lib/startback/operation.rb
CHANGED
@@ -1,16 +1,45 @@
|
|
1
1
|
module Startback
|
2
|
+
#
|
3
|
+
# High-level Operation abstraction, that is a piece of code that executes
|
4
|
+
# on demand and (generally) changes the state of the software system.
|
5
|
+
#
|
6
|
+
# An operation is basically an object that respond to `call`, but that
|
7
|
+
# executes within a given world (see `bind`). It also has before and
|
8
|
+
# after hooks that allows specifying what needs to be done before invoking
|
9
|
+
# call and after having invoked it. All this protocol is actually under
|
10
|
+
# the responsibility of an `OperationRunner`. Operations should not be
|
11
|
+
# called manually by third-party code.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# class SayHello < Startback::Operation
|
16
|
+
#
|
17
|
+
# before_call do
|
18
|
+
# # e.g. check_some_permissions
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def call
|
22
|
+
# puts "Hello"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# after_call do
|
26
|
+
# # e.g. log and/or emit something on a bus
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# end
|
30
|
+
#
|
2
31
|
class Operation
|
3
32
|
include Errors
|
33
|
+
include Support::OperationRunner
|
34
|
+
include Support::Hooks.new(:call)
|
4
35
|
|
5
36
|
attr_accessor :world
|
6
|
-
|
7
37
|
protected :world=
|
8
38
|
|
9
39
|
def bind(world)
|
10
40
|
return self unless world
|
11
|
-
|
12
|
-
|
13
|
-
}
|
41
|
+
self.world = world
|
42
|
+
self
|
14
43
|
end
|
15
44
|
|
16
45
|
def method_missing(name, *args, &bl)
|
@@ -19,10 +48,14 @@ module Startback
|
|
19
48
|
world.fetch(name){ super }
|
20
49
|
end
|
21
50
|
|
51
|
+
def respond_to?(name, *args)
|
52
|
+
super || (world && world.has_key?(name))
|
53
|
+
end
|
54
|
+
|
22
55
|
protected
|
23
56
|
|
24
|
-
def
|
25
|
-
|
57
|
+
def operation_world(op)
|
58
|
+
self.world
|
26
59
|
end
|
27
60
|
|
28
61
|
end # class Operation
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class FakeLogger < Logger
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@last_msg = nil
|
7
|
+
end
|
8
|
+
attr_reader :last_msg
|
9
|
+
|
10
|
+
[:debug, :info, :warn, :error, :fatal].each do |meth|
|
11
|
+
define_method(meth) do |msg|
|
12
|
+
@last_msg = msg
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end # class Logger
|
17
|
+
end # module Support
|
18
|
+
end # module Startback
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class Hooks < Module
|
4
|
+
|
5
|
+
def initialize(suffix)
|
6
|
+
@suffix = suffix
|
7
|
+
define_method :"before_#{suffix}" do
|
8
|
+
self.class.__befores.each do |bl|
|
9
|
+
instance_exec(&bl)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
define_method :"after_#{suffix}" do
|
13
|
+
self.class.__afters.each do |bl|
|
14
|
+
instance_exec(&bl)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
attr_reader :suffix
|
19
|
+
|
20
|
+
def included(by)
|
21
|
+
by.instance_eval %Q{
|
22
|
+
def __befores(create = false)
|
23
|
+
if create
|
24
|
+
@__befores ||= (superclass.respond_to?(:__befores, false) ? superclass.__befores.dup : [])
|
25
|
+
end
|
26
|
+
@__befores || (superclass.respond_to?(:__befores, false) ? superclass.__befores : [])
|
27
|
+
end
|
28
|
+
|
29
|
+
def __afters(create = false)
|
30
|
+
if create
|
31
|
+
@__afters ||= (superclass.respond_to?(:__afters, false) ? superclass.__afters.dup : [])
|
32
|
+
end
|
33
|
+
@__afters || (superclass.respond_to?(:__afters, false) ? superclass.__afters : [])
|
34
|
+
end
|
35
|
+
|
36
|
+
def before_#{suffix}(&bl)
|
37
|
+
__befores(true) << bl
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_#{suffix}(&bl)
|
41
|
+
__afters(true) << bl
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
end # class Hooks
|
47
|
+
end # module Support
|
48
|
+
end # module Startback
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
#
|
4
|
+
# Support module for high-level architectural components that
|
5
|
+
# execute operations as part of their logic, see e.g. Web::Api.
|
6
|
+
#
|
7
|
+
# This module contributes a `run` instance method that allows
|
8
|
+
# binding an operation with a world, and executing it while
|
9
|
+
# supporting around runners.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# class HighLevelComponent
|
14
|
+
# include Startback::Support::OperationRunner
|
15
|
+
#
|
16
|
+
# def some_method
|
17
|
+
# # Runs the operation passed after some binding
|
18
|
+
# run SomeOperation.new
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# protected
|
22
|
+
#
|
23
|
+
# # Overriden to inject some extra world
|
24
|
+
# def operation_world(op)
|
25
|
+
# super(op).merge({ hello: "world" })
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # Execute this around op
|
29
|
+
# around_run do |op, then_block|
|
30
|
+
# puts "About to run #{op.inspect}"
|
31
|
+
# then_block.call
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # SomeClass#call will be called with the operation
|
35
|
+
# # as first parameter and a block as continuation
|
36
|
+
# around_run SomeClass.new
|
37
|
+
#
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
module OperationRunner
|
41
|
+
|
42
|
+
# Contributes the hook DSL methods to classes that include
|
43
|
+
# the OperationRunner module
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
# Registers a callable to be executed around operation running.
|
47
|
+
#
|
48
|
+
# In its block form, the callable is `instance_exec`uted on the
|
49
|
+
# runner instance, with the operation passed as first parameter
|
50
|
+
# and a then_block callable as second parameter (continuation):
|
51
|
+
#
|
52
|
+
# around_run do |op,then_block|
|
53
|
+
# # do whatever you want with the op (already bounded)
|
54
|
+
# puts op.inspect
|
55
|
+
#
|
56
|
+
# # do not forget to call the continuation block
|
57
|
+
# then_block.call
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# With a parameter responding to `#call`, the latter is invoked
|
61
|
+
# with the operation as parameter and a block as continuation:
|
62
|
+
#
|
63
|
+
# class Arounder
|
64
|
+
#
|
65
|
+
# def call(op)
|
66
|
+
# # do whatever you want with the op (already bounded)
|
67
|
+
# puts op.inspect
|
68
|
+
#
|
69
|
+
# # do not forget to call the continuation block
|
70
|
+
# yield
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
def around_run(arounder = nil, &bl)
|
76
|
+
raise ArgumentError, "Arg or block required" unless arounder || bl
|
77
|
+
arounds(true) << [arounder || bl, arounder.nil?]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def arounds(create = false)
|
83
|
+
if create
|
84
|
+
@arounds ||= superclass.respond_to?(:arounds, true) \
|
85
|
+
? superclass.send(:arounds, true).dup \
|
86
|
+
: []
|
87
|
+
end
|
88
|
+
@arounds || (superclass.respond_to?(:arounds, true) ? superclass.send(:arounds, true) : [])
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
# When included by a class/module, install the DSL methods
|
94
|
+
def self.included(by)
|
95
|
+
by.extend(ClassMethods)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Runs `operation`, taking care of binding it and executing
|
99
|
+
# hooks.
|
100
|
+
#
|
101
|
+
# This method is NOT intended to be overriden. Use hooks and
|
102
|
+
# `operation_world` to impact default behavior.
|
103
|
+
def run(operation)
|
104
|
+
op_world = operation_world(operation)
|
105
|
+
op_bound = operation.bind(op_world)
|
106
|
+
_run_befores(op_bound)
|
107
|
+
r = _run_with_arounds(op_bound, self.class.send(:arounds))
|
108
|
+
_run_afters(op_bound)
|
109
|
+
r
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
# Returns the world to use to bind an operation.
|
115
|
+
#
|
116
|
+
# The default implementation returns an empty hash. This is
|
117
|
+
# intended to be overriden by classes including this module.
|
118
|
+
def operation_world(op)
|
119
|
+
{}
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def _run_befores(op_bound)
|
125
|
+
op_bound.before_call if op_bound.respond_to?(:before_call, true)
|
126
|
+
end
|
127
|
+
|
128
|
+
def _run_with_arounds(operation, arounds = [])
|
129
|
+
if arounds.empty?
|
130
|
+
operation.call
|
131
|
+
else
|
132
|
+
arounder, iexec = arounds.first
|
133
|
+
after_first = ->() {
|
134
|
+
_run_with_arounds(operation, arounds[1..-1])
|
135
|
+
}
|
136
|
+
if iexec
|
137
|
+
self.instance_exec(operation, after_first, &arounder)
|
138
|
+
else
|
139
|
+
arounder.call(self, operation, &after_first)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def _run_afters(op_bound)
|
145
|
+
op_bound.after_call if op_bound.respond_to?(:after_call, true)
|
146
|
+
end
|
147
|
+
|
148
|
+
end # module OperationRunner
|
149
|
+
end # module Support
|
150
|
+
end # module Startback
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
#
|
4
|
+
# This module provides helper methods for robustness of a software design.
|
5
|
+
#
|
6
|
+
# It is included by main Startback abstractions, and can be included by
|
7
|
+
# specific software components who needs fine-tuning of monitoring, logging
|
8
|
+
# and error handling.
|
9
|
+
#
|
10
|
+
# All public methods here follow the following free args parameters:
|
11
|
+
#
|
12
|
+
# 1. First (& second) argument(s) form the log message.
|
13
|
+
#
|
14
|
+
# A full log message is a Hash having :op (required), :op_took (optional),
|
15
|
+
# and :op_data (optional) keys.
|
16
|
+
#
|
17
|
+
# If a String (or two) are used instead, a log message will be built taking
|
18
|
+
# the former as the executer (a class or instance) and the second as a method.
|
19
|
+
# `{ op: "executer#method" }`
|
20
|
+
#
|
21
|
+
# 2. The second (or third) argument should be a Logger instance, a Context,
|
22
|
+
# or an instance knowing its context. The best logger is extracted from it
|
23
|
+
# and used for actual logging.
|
24
|
+
#
|
25
|
+
# Examples:
|
26
|
+
#
|
27
|
+
# log(op: "hello", op_data: {foo: 12}) => logged as such on STDOUT
|
28
|
+
# log("A simple message") => { op: "A simple message" } on STDOUT
|
29
|
+
# log(Startback, "hello") => { op: "Startback#hello" } on STDOUT
|
30
|
+
# log(Event.new, "hello") => { op: "Event#hello" } on STDOUT
|
31
|
+
# log(self, context) => { op: "..." } on context's logger or STDOUT
|
32
|
+
# log(self, event) => { op: "..." } on event context's logger or STDOUT
|
33
|
+
# ...
|
34
|
+
#
|
35
|
+
module Robustness
|
36
|
+
|
37
|
+
# Included to avoid poluting the space of the including
|
38
|
+
# classes.
|
39
|
+
module Tools
|
40
|
+
|
41
|
+
def default_logger
|
42
|
+
@@default_logger ||= ::Logger.new(STDOUT)
|
43
|
+
from = caller.reject{|x| x =~ /lib\/startback/ }.first
|
44
|
+
@@default_logger.debug("Watch out, using default logger from: #{from}")
|
45
|
+
@@default_logger
|
46
|
+
end
|
47
|
+
module_function :default_logger
|
48
|
+
|
49
|
+
def logger_for(arg)
|
50
|
+
return arg if arg.is_a?(::Logger)
|
51
|
+
return arg.logger if arg.is_a?(Context) && arg.logger
|
52
|
+
return logger_for(arg.context) if arg.respond_to?(:context, false)
|
53
|
+
default_logger
|
54
|
+
end
|
55
|
+
module_function :logger_for
|
56
|
+
|
57
|
+
def parse_args(log_msg, method = nil, context = nil, extra = nil)
|
58
|
+
method, context, extra = nil, method, context unless method.is_a?(String)
|
59
|
+
context, extra = nil, context if context.is_a?(Hash) && extra.nil?
|
60
|
+
logger = logger_for(context) || logger_for(log_msg)
|
61
|
+
log_msg = if log_msg.is_a?(Hash)
|
62
|
+
log_msg.dup
|
63
|
+
elsif log_msg.is_a?(String)
|
64
|
+
log_msg = { op: "#{log_msg}#{method.nil? ? '' : '#'+method.to_s}" }
|
65
|
+
else
|
66
|
+
log_msg = log_msg.class unless log_msg.is_a?(Module)
|
67
|
+
log_msg = { op: "#{log_msg.name}##{method}" }
|
68
|
+
end
|
69
|
+
log_msg.merge!(extra) if extra
|
70
|
+
[ log_msg, logger ]
|
71
|
+
end
|
72
|
+
module_function :parse_args
|
73
|
+
|
74
|
+
[:debug, :info, :warn, :error, :fatal].each do |meth|
|
75
|
+
define_method(meth) do |args, extra = nil, &bl|
|
76
|
+
act_args = args + [extra]
|
77
|
+
log_msg, logger = parse_args(*act_args)
|
78
|
+
logger.send(meth, log_msg)
|
79
|
+
end
|
80
|
+
module_function(meth)
|
81
|
+
end
|
82
|
+
|
83
|
+
end # module Tools
|
84
|
+
|
85
|
+
# Logs a specific message with a given severity.
|
86
|
+
#
|
87
|
+
# Severity can be :debug, :info, :warn, :error or :fatal.
|
88
|
+
# The args must follow module's conventions, see above.
|
89
|
+
def log(severity, *args)
|
90
|
+
Tools.send(severity, args)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Calls the block and monitors then log its execution time.
|
94
|
+
#
|
95
|
+
# The args must follow module's conventions, see above.
|
96
|
+
def monitor(*args, &bl)
|
97
|
+
result = nil
|
98
|
+
took = Benchmark.realtime {
|
99
|
+
result = bl.call
|
100
|
+
}
|
101
|
+
Tools.debug(args, op_took: took)
|
102
|
+
result
|
103
|
+
rescue => ex
|
104
|
+
Tools.error(args, error: ex)
|
105
|
+
raise
|
106
|
+
end
|
107
|
+
|
108
|
+
# Executes the block without letting errors propagate.
|
109
|
+
# Errors are logged, though. Nothing is logged if everything
|
110
|
+
# goes fine.
|
111
|
+
#
|
112
|
+
# The args must follow module's conventions, see above.
|
113
|
+
def stop_errors(*args, &bl)
|
114
|
+
result = nil
|
115
|
+
took = Benchmark.realtime {
|
116
|
+
result = bl.call
|
117
|
+
}
|
118
|
+
result
|
119
|
+
rescue => ex
|
120
|
+
Tools.error(args, op_took: took, error: ex)
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Tries executing the block up to `n` times, until an attempt
|
125
|
+
# succeeds (then returning the result). Logs the first and last
|
126
|
+
# fatal error, if any.
|
127
|
+
#
|
128
|
+
# The args must follow module's conventions, see above.
|
129
|
+
def try_max_times(n, *args, &bl)
|
130
|
+
retried = 0
|
131
|
+
took = 0
|
132
|
+
begin
|
133
|
+
result = nil
|
134
|
+
took += Benchmark.realtime {
|
135
|
+
result = bl.call
|
136
|
+
}
|
137
|
+
result
|
138
|
+
rescue => ex
|
139
|
+
Tools.error(args + [{op_took: took, error: ex}]) if retried == 0
|
140
|
+
retried += 1
|
141
|
+
if retried < n
|
142
|
+
sleep(retried)
|
143
|
+
retry
|
144
|
+
else
|
145
|
+
Tools.fatal(args + [{op_took: took, error: ex}])
|
146
|
+
raise
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end # module Robustness
|
152
|
+
end # module Support
|
153
|
+
end # module Startback
|
data/lib/startback/support.rb
CHANGED
data/lib/startback/version.rb
CHANGED
data/lib/startback/web/api.rb
CHANGED
@@ -20,11 +20,10 @@ module Startback
|
|
20
20
|
###
|
21
21
|
### Facade over third party tools
|
22
22
|
###
|
23
|
+
include Support::OperationRunner
|
23
24
|
|
24
|
-
def
|
25
|
-
|
26
|
-
.bind({ context: context })
|
27
|
-
.call
|
25
|
+
def operation_world(op)
|
26
|
+
{ context: context }
|
28
27
|
end
|
29
28
|
|
30
29
|
###
|
@@ -19,6 +19,7 @@ module Startback
|
|
19
19
|
#
|
20
20
|
class CatchAll < Rack::Robustness
|
21
21
|
include Errors
|
22
|
+
include Support::Robustness
|
22
23
|
|
23
24
|
FATAL_ERROR = {
|
24
25
|
code: "Startback::Errors::InternalServerError",
|
@@ -32,12 +33,18 @@ module Startback
|
|
32
33
|
self.body FATAL_ERROR
|
33
34
|
|
34
35
|
self.ensure(true) do |ex|
|
36
|
+
STDERR.puts(ex.message)
|
35
37
|
context = env[Context::Middleware::RACK_ENV_KEY]
|
36
|
-
|
37
|
-
context.error_handler.
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
begin
|
39
|
+
if context && context.respond_to?(:error_handler, true) && context.error_handler
|
40
|
+
context.error_handler.fatal(ex)
|
41
|
+
else
|
42
|
+
log(:fatal, self, "ensure", error: ex)
|
43
|
+
end
|
44
|
+
rescue => ex2
|
45
|
+
STDERR.puts(ex2.message)
|
46
|
+
STDERR.puts(ex2.backtrace[0..10].join("\n"))
|
47
|
+
raise
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Startback
|
2
|
+
module Web
|
3
|
+
module Middleware
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def context(env = @env)
|
8
|
+
::Startback::Context::Middleware.context(env) || Errors.server_error!("Unable to find context!!")
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module Middleware
|
12
|
+
end # module Web
|
13
|
+
end # module Startback
|
data/lib/startback.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startback/audit'
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe Trailer do
|
6
|
+
|
7
|
+
let(:trailer) {
|
8
|
+
Trailer.new("/tmp/trail.log")
|
9
|
+
}
|
10
|
+
|
11
|
+
describe "op_data" do
|
12
|
+
|
13
|
+
def op_data(op, trailer = self.trailer)
|
14
|
+
trailer.send(:op_data, op)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'uses to_trail in priority if provided' do
|
18
|
+
op = OpenStruct.new(to_trail: { foo: "bar" }, input: 12, request: 13)
|
19
|
+
expect(op_data(op)).to eql({ foo: "bar" })
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'uses input then' do
|
23
|
+
op = OpenStruct.new(input: { foo: "bar" }, request: 13)
|
24
|
+
expect(op_data(op)).to eql({ foo: "bar" })
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'uses request then' do
|
28
|
+
op = OpenStruct.new(request: { foo: "bar" })
|
29
|
+
expect(op_data(op)).to eql({ foo: "bar" })
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'applies default blacklists for security reasons' do
|
33
|
+
op = OpenStruct.new(input: {
|
34
|
+
token: "will not be dumped",
|
35
|
+
a_token: "will not be dumped",
|
36
|
+
AToken: "will not be dumped",
|
37
|
+
password: "will not be dumped",
|
38
|
+
secret: "will not be dumped",
|
39
|
+
credentials: "will not be dumped",
|
40
|
+
foo: "bar"
|
41
|
+
})
|
42
|
+
expect(op_data(op)).to eql({
|
43
|
+
foo: "bar"
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'applies default blacklists to data arrays too' do
|
48
|
+
op = OpenStruct.new(input: [{
|
49
|
+
token: "will not be dumped",
|
50
|
+
a_token: "will not be dumped",
|
51
|
+
AToken: "will not be dumped",
|
52
|
+
password: "will not be dumped",
|
53
|
+
secret: "will not be dumped",
|
54
|
+
credentials: "will not be dumped",
|
55
|
+
foo: "bar"
|
56
|
+
}])
|
57
|
+
expect(op_data(op)).to eql([{
|
58
|
+
foo: "bar"
|
59
|
+
}])
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'uses the stop words provided at construction' do
|
63
|
+
t = Trailer.new("/tmp/trail.log", blacklist: "hello and world")
|
64
|
+
op = OpenStruct.new(request: { Hello: "bar", World: "foo", foo: "bar" })
|
65
|
+
expect(op_data(op, t)).to eql({ foo: "bar" })
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "op_context" do
|
71
|
+
|
72
|
+
def op_context(op, trailer = self.trailer)
|
73
|
+
trailer.send(:op_context, op)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'applies default blacklists for security reasons' do
|
77
|
+
op = OpenStruct.new(context: {
|
78
|
+
token: "will not be dumped",
|
79
|
+
foo: "bar"
|
80
|
+
})
|
81
|
+
expect(op_context(op)).to eql({ foo: "bar" })
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|