chassis 0.1.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.
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