chassis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +362 -0
  6. data/Rakefile +33 -0
  7. data/chassis.gemspec +41 -0
  8. data/examples/repo.rb +40 -0
  9. data/lib/chassis.rb +81 -0
  10. data/lib/chassis/array_utils.rb +8 -0
  11. data/lib/chassis/circuit_panel.rb +22 -0
  12. data/lib/chassis/core_ext/array.rb +5 -0
  13. data/lib/chassis/core_ext/hash.rb +5 -0
  14. data/lib/chassis/core_ext/string.rb +13 -0
  15. data/lib/chassis/delegate.rb +29 -0
  16. data/lib/chassis/dirty_session.rb +105 -0
  17. data/lib/chassis/error.rb +7 -0
  18. data/lib/chassis/faraday.rb +226 -0
  19. data/lib/chassis/form.rb +56 -0
  20. data/lib/chassis/hash_utils.rb +16 -0
  21. data/lib/chassis/heroku.rb +5 -0
  22. data/lib/chassis/initializable.rb +11 -0
  23. data/lib/chassis/logger.rb +8 -0
  24. data/lib/chassis/observable.rb +19 -0
  25. data/lib/chassis/persistence.rb +49 -0
  26. data/lib/chassis/rack/bouncer.rb +33 -0
  27. data/lib/chassis/rack/builder_shim_patch.rb +7 -0
  28. data/lib/chassis/rack/health_check.rb +45 -0
  29. data/lib/chassis/rack/instrumentation.rb +20 -0
  30. data/lib/chassis/rack/json_body_parser.rb +20 -0
  31. data/lib/chassis/rack/no_robots.rb +24 -0
  32. data/lib/chassis/registry.rb +30 -0
  33. data/lib/chassis/repo.rb +73 -0
  34. data/lib/chassis/repo/base_repo.rb +99 -0
  35. data/lib/chassis/repo/delegation.rb +78 -0
  36. data/lib/chassis/repo/lazy_association.rb +57 -0
  37. data/lib/chassis/repo/memory_repo.rb +7 -0
  38. data/lib/chassis/repo/null_repo.rb +64 -0
  39. data/lib/chassis/repo/pstore_repo.rb +54 -0
  40. data/lib/chassis/repo/record_map.rb +44 -0
  41. data/lib/chassis/repo/redis_repo.rb +55 -0
  42. data/lib/chassis/serializable.rb +52 -0
  43. data/lib/chassis/string_utils.rb +50 -0
  44. data/lib/chassis/version.rb +3 -0
  45. data/lib/chassis/web_service.rb +61 -0
  46. data/test/array_utils_test.rb +23 -0
  47. data/test/chassis_test.rb +7 -0
  48. data/test/circuit_panel_test.rb +22 -0
  49. data/test/core_ext/array_test.rb +8 -0
  50. data/test/core_ext/hash_test.rb +8 -0
  51. data/test/core_ext/string_test.rb +16 -0
  52. data/test/delegate_test.rb +41 -0
  53. data/test/dirty_session_test.rb +138 -0
  54. data/test/error_test.rb +12 -0
  55. data/test/faraday_test.rb +749 -0
  56. data/test/form_test.rb +29 -0
  57. data/test/hash_utils_test.rb +17 -0
  58. data/test/initializable_test.rb +22 -0
  59. data/test/logger_test.rb +43 -0
  60. data/test/observable_test.rb +27 -0
  61. data/test/persistence_test.rb +112 -0
  62. data/test/prox_test.rb +7 -0
  63. data/test/rack/bouncer_test.rb +42 -0
  64. data/test/rack/builder_patch_test.rb +36 -0
  65. data/test/rack/health_check_test.rb +35 -0
  66. data/test/rack/instrumentation_test.rb +38 -0
  67. data/test/rack/json_body_parser_test.rb +38 -0
  68. data/test/rack/no_robots_test.rb +34 -0
  69. data/test/registry_test.rb +26 -0
  70. data/test/repo/delegation_test.rb +101 -0
  71. data/test/repo/lazy_association_test.rb +115 -0
  72. data/test/repo/memory_repo_test.rb +25 -0
  73. data/test/repo/null_repo_test.rb +48 -0
  74. data/test/repo/pstore_repo_test.rb +28 -0
  75. data/test/repo/redis_repo_test.rb +26 -0
  76. data/test/repo/repo_tests.rb +120 -0
  77. data/test/repo_test.rb +76 -0
  78. data/test/serializable_test.rb +77 -0
  79. data/test/string_utils_test.rb +21 -0
  80. data/test/test_helper.rb +10 -0
  81. data/test/web_service_test.rb +107 -0
  82. metadata +426 -0
@@ -0,0 +1,29 @@
1
+ require_relative 'test_helper'
2
+
3
+ class FormTest < MiniTest::Unit::TestCase
4
+ class Form
5
+ include Chassis.form
6
+
7
+ attribute :name, String
8
+ attribute :email, String
9
+ end
10
+
11
+ def test_initializer_takes_a_block
12
+ form = Form.new do |f|
13
+ f.name = 'Adam'
14
+ end
15
+
16
+ assert_equal 'Adam', form.name
17
+ end
18
+
19
+ def test_attributes_only_includes_dirty_attributes
20
+ form = Form.new name: 'Adam'
21
+ assert_equal({ name: 'Adam' }, form.values)
22
+ end
23
+
24
+ def test_assigning_an_known_attributes_raises_an_error
25
+ assert_raises Chassis::UnknownFormFieldError do
26
+ Form.new foo: 'bar'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'test_helper'
2
+
3
+ class HashUtilsTest < MiniTest::Unit::TestCase
4
+ def utils
5
+ Chassis::HashUtils
6
+ end
7
+
8
+ def test_symbolize_keys_converts_keys_to_symbols
9
+ result = utils.symbolize({ 'foo' => 'bar' })
10
+ assert_equal({ foo: 'bar'}, result)
11
+ end
12
+
13
+ def test_symbolize_keys_recursises_into_objects
14
+ result = utils.symbolize({ 'foo' => { 'bar' => 'baz' }})
15
+ assert_equal({ foo: { bar: 'baz' }}, result)
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'test_helper'
2
+
3
+ class InitializableTest < MiniTest::Unit::TestCase
4
+ class Person
5
+ include Chassis::Initializable
6
+
7
+ attr_accessor :nick
8
+ end
9
+
10
+ def test_sets_attributes
11
+ person = Person.new nick: 'ahawkins'
12
+ assert_equal 'ahawkins', person.nick
13
+ end
14
+
15
+ def test_works_with_a_block
16
+ person = Person.new do |person|
17
+ person.nick = 'ahawkins'
18
+ end
19
+
20
+ assert_equal 'ahawkins', person.nick
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require_relative 'test_helper'
2
+
3
+ class LoggerTest < MiniTest::Unit::TestCase
4
+ def test_log_level_defaults_to_env_variable
5
+ with_env :warn do
6
+ logger = Chassis::Logger.new $stdout
7
+ assert_equal Logger::WARN, logger.level
8
+ end
9
+ end
10
+
11
+ def test_log_deafults_debug_without_env_variable
12
+ refute ENV['LOG_LEVEL'], "Precondition: LOG_LEVEL must be blank"
13
+
14
+ logger = Chassis::Logger.new $stdout
15
+ assert_equal Logger::DEBUG, logger.level
16
+ end
17
+
18
+ def test_log_dev_defaults_to_chassis_stream
19
+ Chassis.stream = StringIO.new
20
+
21
+ logger = Chassis::Logger.new
22
+ logger.debug 'test'
23
+
24
+ Chassis.stream.rewind
25
+ content = Chassis.stream.read
26
+
27
+ assert_includes content, 'test'
28
+ end
29
+
30
+ private
31
+ def with_env(name)
32
+ original_env = ENV['LOG_LEVEL']
33
+ ENV['LOG_LEVEL'] = name.to_s
34
+
35
+ yield
36
+ ensure
37
+ if original_env
38
+ ENV['LOG_LEVEL'] = original_env
39
+ else
40
+ ENV.delete 'LOG_LEVEL'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ObservableTest < MiniTest::Unit::TestCase
4
+ class Ship
5
+ include Chassis::Observable
6
+
7
+ def sink!(command)
8
+ notify_observers :sink, command
9
+ end
10
+ end
11
+
12
+ class Watchtower
13
+ def sink(ship, command)
14
+
15
+ end
16
+ end
17
+
18
+ def test_includes_the_object_in_notifications
19
+ ship = Ship.new
20
+ observer = Watchtower.new
21
+
22
+ observer.expects(:sink).with(ship, 'abandon ship!')
23
+
24
+ ship.add_observer observer
25
+ ship.sink! 'abandon ship!'
26
+ end
27
+ end
@@ -0,0 +1,112 @@
1
+ require_relative "test_helper"
2
+
3
+ class PersistenceTest < MiniTest::Unit::TestCase
4
+ class FakeRepo
5
+
6
+ end
7
+
8
+ class Model
9
+ include Chassis::Persistence
10
+
11
+ class << self
12
+ def repo
13
+ FakeRepo
14
+ end
15
+ end
16
+ end
17
+
18
+ def test_models_can_be_initialize_with_a_hash
19
+ assert_includes Model.ancestors, Chassis::Initializable
20
+ end
21
+
22
+ def test_class_has_a_create_factory
23
+ FakeRepo.expects(:save)
24
+ Model.create id: 5
25
+ end
26
+
27
+ def test_delegates_save_to_the_repo
28
+ model = Model.new id: 5
29
+ FakeRepo.expects(:save).with(model)
30
+ model.save
31
+ end
32
+
33
+ def test_delegates_delete_to_the_repo
34
+ model = Model.new id: 5
35
+ FakeRepo.expects(:delete).with(model)
36
+ model.delete
37
+ end
38
+
39
+ def test_is_new_record_when_id_is_nil
40
+ model = Model.new id: 5
41
+ refute model.new_record?
42
+
43
+ model.id = nil
44
+ assert model.new_record?
45
+ end
46
+
47
+ def test_implements_double_equals
48
+ m1 = Model.new(id: 1)
49
+ m2 = Model.new(id: 2)
50
+ m3 = Model.new(id: 1)
51
+
52
+ assert (m1 == m1)
53
+ assert (m1 == m3)
54
+ refute (m1 == m2)
55
+ end
56
+
57
+ def test_implements_triple_equals
58
+ m1 = Model.new(id: 1)
59
+ m2 = Model.new(id: 2)
60
+ m3 = Model.new(id: 1)
61
+
62
+ assert (m1 === m1)
63
+ assert (m1 === m3)
64
+ refute (m1 === m2)
65
+ end
66
+
67
+ def test_implements_eql?
68
+ m1 = Model.new(id: 1)
69
+ m2 = Model.new(id: 2)
70
+ m3 = Model.new(id: 1)
71
+
72
+ assert (m1 == m1)
73
+ assert (m1 == m3)
74
+ refute (m1 == m2)
75
+ end
76
+
77
+ def test_acts_has_a_hash_key
78
+ m1 = Model.new(id: 1)
79
+ m2 = Model.new(id: 2)
80
+ m3 = Model.new(id: 1)
81
+
82
+ hash = { }
83
+ hash[m1] = 'm1'
84
+
85
+ assert_includes hash, m1
86
+ assert_includes hash, m3
87
+ refute_includes hash, m2
88
+ end
89
+
90
+ def test_acts_well_in_arrays
91
+ m1 = Model.new(id: 1)
92
+ m2 = Model.new(id: 2)
93
+ m3 = Model.new(id: 1)
94
+
95
+ array = [ m1 ]
96
+
97
+ assert_includes array, m1
98
+ assert_includes array, m3
99
+ refute_includes array, m2
100
+ end
101
+
102
+ def test_save_takes_a_block_of_changes
103
+ FakeRepo.expects(:save)
104
+
105
+ model = Model.new id: 5
106
+ model.save do |m|
107
+ m.id = 6
108
+ end
109
+
110
+ assert_equal 6, model.id
111
+ end
112
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ProxTest < MiniTest::Unit::TestCase
4
+ def test_prox_is_assigned_to_proxy
5
+ assert_equal Chassis::Proxy, Prox
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../test_helper'
2
+
3
+ class RackBouncer < MiniTest::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class HelloWorld
7
+ def call(env)
8
+ [200, {'Content-Type' => 'text/plain' }, 'Hi']
9
+ end
10
+ end
11
+
12
+ attr_reader :app
13
+
14
+ def setup
15
+ builder = Rack::Builder.new
16
+ builder.use Chassis::Rack::Bouncer
17
+ builder.run HelloWorld.new
18
+ @app = builder.to_app
19
+ end
20
+
21
+ def test_bouncers_masscan
22
+ get '/', { } , 'HTTP_USER_AGENT' => 'masscan/1.0 (https://github.com/robertdavidgraham/masscan)'
23
+ assert_equal 403, last_response.status
24
+ end
25
+
26
+ def test_can_supply_a_custom_bouncer
27
+ builder = Rack::Builder.new
28
+ builder.use Chassis::Rack::Bouncer do |req|
29
+ true
30
+ end
31
+ builder.run HelloWorld.new
32
+ @app = builder.to_app
33
+
34
+ get '/'
35
+ assert_equal 403, last_response.status
36
+ end
37
+
38
+ def test_allows_other_requests
39
+ get '/foo'
40
+ assert_equal 'Hi', last_response.body
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../test_helper'
2
+
3
+ class RackBuilderPatchTest < MiniTest::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class OrderPrinter
7
+ def call(env)
8
+ [200, {'Content-Type' => 'text/plain'}, [ env['order'].join(',') ]]
9
+ end
10
+ end
11
+
12
+ class OrderedMiddleware
13
+ def initialize(app, position)
14
+ @app, @position = app, position
15
+ end
16
+
17
+ def call(env)
18
+ env['order'] ||= []
19
+ env['order'] << @position
20
+ @app.call env
21
+ end
22
+ end
23
+
24
+ attr_reader :app
25
+
26
+ def test_should_be_able_to_insert_middlewares_at_the_top
27
+ @app = Rack::Builder.app do
28
+ shim OrderedMiddleware, 2
29
+ shim OrderedMiddleware, 1
30
+ run OrderPrinter.new
31
+ end
32
+
33
+ get '/'
34
+ assert_equal '1,2', last_response.body
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../test_helper'
2
+
3
+ class HealthCheckTest < MiniTest::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class HelloWorld
7
+ def call(env)
8
+ [200, {'Content-Type' => 'text/plain' }, 'Hi']
9
+ end
10
+ end
11
+
12
+ attr_reader :log, :app
13
+
14
+ def setup
15
+ @log = StringIO.new
16
+ builder = Rack::Builder.new
17
+ builder.use Chassis::Rack::Ping
18
+ builder.run HelloWorld.new
19
+ @app = builder.to_app
20
+ end
21
+
22
+ def test_returns_status_when_accessing_ping
23
+ get '/ping'
24
+
25
+ assert_equal 200, last_response.status
26
+ assert_equal 'text/plain', last_response.content_type
27
+ assert_equal 'pong', last_response.body
28
+ end
29
+
30
+ def test_allows_other_requests
31
+ get '/foo'
32
+
33
+ assert_equal 'Hi', last_response.body
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../test_helper'
2
+
3
+ class RackInstrumentationTest < MiniTest::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class HelloWorld
7
+ def call(env)
8
+ [200, {'Content-Type' => 'text/plain' }, 'Hi']
9
+ end
10
+ end
11
+
12
+ attr_reader :app
13
+
14
+ def setup
15
+ Harness.config.collector = Harness::FakeCollector.new
16
+ Harness.config.queue = Harness::SyncQueue.new
17
+
18
+ builder = Rack::Builder.new
19
+ builder.use Chassis::Rack::Instrumentation
20
+ builder.run HelloWorld.new
21
+ @app = builder.to_app
22
+ end
23
+
24
+ def test_adds_the_x_runtime_header
25
+ get '/foo'
26
+
27
+ assert_equal 'Hi', last_response.body
28
+
29
+ assert last_response['X-Runtime']
30
+ end
31
+
32
+ def test_instruments_with_harness
33
+ get '/foo'
34
+
35
+ refute_empty Harness.collector.timers
36
+ assert_equal 'rack.request.all', Harness.collector.timers.first.name
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../test_helper'
2
+
3
+ class JsonBodyParserTest < MiniTest::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class EchoServer
7
+ def call(env)
8
+ request = Rack::Request.new env
9
+ [200, {'Content-Type' => 'text/plain'}, [JSON.dump(request.params)]]
10
+ end
11
+ end
12
+
13
+ attr_reader :app
14
+
15
+ def setup
16
+ @app = Rack::Builder.app do
17
+ use Chassis::Rack::JsonBodyParser
18
+ run EchoServer.new
19
+ end
20
+ end
21
+
22
+ def test_parses_json_requests
23
+ post '/', JSON.dump({foo: 'bar'}), 'CONTENT_TYPE' => 'application/json'
24
+
25
+ params = JSON.load last_response.body
26
+ assert_equal 'bar', params.fetch('foo')
27
+ end
28
+
29
+ def test_handles_empty_get_requests
30
+ get '/', { }, { 'CONTENT_TYPE' => 'application/json' }
31
+ assert_equal 200, last_response.status
32
+ end
33
+
34
+ def test_json_parse_errors_return_400
35
+ post '/', 'foo=bar', 'CONTENT_TYPE' => 'application/json'
36
+ assert_equal 400, last_response.status
37
+ end
38
+ end