doppelserver 0.4.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4a3d986a23043595f710f3e936d015db53958202601dd8a609cc6c8734480ac
4
- data.tar.gz: 790559c7e271aa596b2820009feed9d72f20cbab3767025b70f2692ec56e62c7
3
+ metadata.gz: 6b1b219ac52c0632c708b0187d92484a5a1c4881aa80b6cec2e59c317eb8ad7b
4
+ data.tar.gz: 4384f653e8558901121b2a47484a7677b4819dff7b1f5c9e1cc4d638a6b222b2
5
5
  SHA512:
6
- metadata.gz: f46007a28857cde5ae9613b617b34d340edc5e074f5cb5e6db7e9a1e6407c9d7e729c299e1236ce4e9ae2b3f3f3c7bba04d6718512cc2287052d71bf6dc00de8
7
- data.tar.gz: 43df8ba6f069df20bfa4f7a1730269f83b5d93f580691de116a711b34490f090c5df7c1aed6da4cd0cf0b01eb792febd4c7aec324d4d59083a7c6aedd8ea7477
6
+ metadata.gz: a4b89cf1685aff54633bfd56f0c70c415de73a9481f033b60a229bd76278f610c979e296823f716a1454fef1aae43a8b5dca285143e1bd81f2e5fce9b3c2561e
7
+ data.tar.gz: 1f4f8ee391a7431c4982143897d2be8c2df6754049c3a688e8f9496749c0caabfdfe52feedb3e7e20880c9da568e6792b56126e9322fd5e06b46c8d9de3034cd
@@ -1,77 +1,70 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2018-05-26 12:47:01 -0700 using RuboCop version 0.56.0.
3
+ # on 2018-07-12 03:45:51 -0700 using RuboCop version 0.58.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 5
9
+ # Offense count: 7
10
10
  # Configuration parameters: CountComments, ExcludedMethods.
11
+ # ExcludedMethods: refine
11
12
  Metrics/BlockLength:
12
13
  Max: 116
13
14
 
14
- # Offense count: 2
15
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
16
- # URISchemes: http, https
17
- Metrics/LineLength:
18
- Max: 82
19
-
20
15
  # Offense count: 1
21
- Style/ClassVars:
22
- Exclude:
23
- - 'lib/doppelserver/base_server.rb'
24
-
25
- # Offense count: 2
26
- Style/Documentation:
27
- Exclude:
28
- - 'spec/**/*'
29
- - 'test/**/*'
30
- - 'lib/doppelserver/base_server.rb'
31
- - 'lib/doppelserver/data.rb'
32
-
33
- # Offense count: 3
34
- # Cop supports --auto-correct.
35
- Style/ParallelAssignment:
16
+ RSpec/DescribeClass:
36
17
  Exclude:
37
- - 'lib/doppelserver/base_server.rb'
18
+ - 'spec/service_controller_console_spec.rb'
38
19
 
39
- # Offense count: 2
40
- # Cop supports --auto-correct.
41
- # Configuration parameters: EnforcedStyle.
42
- # SupportedStyles: implicit, explicit
43
- Style/RescueStandardError:
20
+ # Offense count: 5
21
+ # Configuration parameters: Max.
22
+ RSpec/ExampleLength:
44
23
  Exclude:
45
- - 'lib/doppelserver/base_server.rb'
24
+ - 'spec/base_server_spec.rb'
46
25
 
47
- # Offense count: 1
48
- # Cop supports --auto-correct.
49
- # Configuration parameters: AllowIfMethodIsEmpty.
50
- Style/SingleLineMethods:
26
+ # Offense count: 6
27
+ RSpec/ExpectInHook:
51
28
  Exclude:
52
29
  - 'spec/base_server_spec.rb'
30
+ - 'spec/service_controller_console_spec.rb'
53
31
 
54
32
  # Offense count: 1
33
+ # Configuration parameters: CustomTransform, IgnoreMethods.
55
34
  RSpec/FilePath:
56
35
  Exclude:
57
36
  - 'spec/base_server_spec.rb'
58
37
 
59
- # Offense count: 5
60
- RSpec/ExampleLength:
61
- Exclude:
62
- - 'spec/base_server_spec.rb'
38
+ # Offense count: 14
39
+ # Configuration parameters: AggregateFailuresByDefault.
40
+ RSpec/MultipleExpectations:
41
+ Max: 4
63
42
 
64
- # Offense count: 5
65
- RSpec/ExpectInHook:
43
+ # Offense count: 1
44
+ RSpec/NestedGroups:
45
+ Max: 4
46
+
47
+ # Offense count: 1
48
+ Style/ClassVars:
66
49
  Exclude:
67
- - 'spec/base_server_spec.rb'
50
+ - 'lib/doppelserver/base_server.rb'
68
51
 
69
- # Offense count: 7
70
- RSpec/MultipleExpectations:
52
+ # Offense count: 1
53
+ Style/Documentation:
71
54
  Exclude:
72
- - 'spec/base_server_spec.rb'
55
+ - 'spec/**/*'
56
+ - 'test/**/*'
57
+ - 'lib/doppelserver/base_server.rb'
73
58
 
74
- # Offense countL 1
75
- RSpec/NestedGroups:
59
+ # Offense count: 1
60
+ # Cop supports --auto-correct.
61
+ # Configuration parameters: AllowIfMethodIsEmpty.
62
+ Style/SingleLineMethods:
76
63
  Exclude:
77
64
  - 'spec/base_server_spec.rb'
65
+
66
+ # Offense count: 1
67
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
68
+ # URISchemes: http, https
69
+ Metrics/LineLength:
70
+ Max: 81
data/README.md CHANGED
@@ -7,15 +7,22 @@
7
7
  [![Gem Version](https://badge.fury.io/rb/doppelserver.svg)](https://badge.fury.io/rb/doppelserver)
8
8
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/dd50d7ee18ae46c38ad053cf3dc59794)](https://www.codacy.com/app/drewcoo/doppelserver?utm_source=github.com&utm_medium=referral&utm_content=drewcoo/doppelserver&utm_campaign=Badge_Grade)
9
9
 
10
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/doppelserver`. To experiment with that code, run `bin/console` for an interactive prompt.
11
10
 
12
- TODO: Delete this and the text above, and describe your gem
11
+ ## What's Doppelerver?
13
12
 
14
- ## Why Doppelerver?
13
+ Welcome to Doppelserver, the fake RESTful service gem, so named because all the cool names were already taken on RubyGems.
15
14
 
16
- TODO: Explain mocks, dups, and fakes. This is a fake. Explain why fakes.
15
+ Chances are you're already using some kind of double in your testing. Test
16
+ doubles fit into several categories:
17
+ - stubs - objects with values chosen to make tests pass
18
+ - mocks - objects with running code that's set to make tests pass
19
+ - spies - objects that can be queried for what happened to them
20
+ - fakes - actual running objects but incomplete and instrumented
21
+
22
+ Martin Fowler [has a good explanation](https://martinfowler.com/articles/mocksArentStubs.html) but he separates what I'm calling stubs into "stubs" that respond with what a test needs and "dummies" that are passed but never accessed.
23
+
24
+ In this case we're talking about a fake service, an actual running service intended to replace what you'd normally have in production for test purposes. It should respond as the actual service would but is instrumented so that you can both ask what happened like a spy and, more importantly, tell it how to respond differently because it's a fake.
17
25
 
18
- Also, all the cool names were taken on RubyGems.
19
26
 
20
27
  ## Installation
21
28
 
@@ -31,23 +38,114 @@ Or install it yourself as:
31
38
 
32
39
  $ gem install doppelserver
33
40
 
41
+
34
42
  ## Usage
35
43
 
36
- Scenarios this should cover:
37
- * Fake a server from your (integration) tests
38
- * Default behaviors
39
- * Imagine a really stupid CRUD database backing the test server,
40
- one that auto-created schema as it went. That's pretty much it.
41
- How? Easy. Instead of a db it's just a hash in memory. Dumb? Yup.
42
- * Overrides
43
- * Control endpoints
44
- * Run interactively (irb/pry console?) while debugging your code
45
- * Record endpoint usage?
46
- * Types of service:
47
- * REST-ish
48
- * GraphQL
49
- * Others? (WSDL?)
50
- * Client bindings? Not sure that makes sense unless it's POROs or x-language.
44
+
45
+ ### Manually Exploring
46
+ There's a command line tool, scc.rb, to explore what Doppelserver does:
47
+
48
+ $ bin/scc.rb help
49
+ Commands:
50
+ scc.rb data # send/retreive all data
51
+ scc.rb help [COMMAND] # Describe available commands or one specific command
52
+ scc.rb restart # restart server, dumping all data
53
+ scc.rb start # start server
54
+ scc.rb stop # stop server
55
+
56
+ Options:
57
+ -p, [--port=PORT] # port service runs on
58
+ # Default: 7357
59
+
60
+
61
+ #### Basics
62
+
63
+ So let's just start it, defaulting to port 7357 (which is leet speak for "test").
64
+
65
+ $ bin/scc.rb start
66
+
67
+ It's now running and you can use your favorite tool to run RESTful queries on it. I'll suggest [Postman](https://www.getpostman.com/) if you don't have anything. The fake service understands plural collections and items in them with (non-negative) integer ids. So you can get the initial things collection:
68
+
69
+ GET http://localhost:7357/things
70
+
71
+ It returns a 200 but no data because it's currently empty. Let's put something in it.
72
+
73
+ POST http://localhost:7357/things
74
+ and send this data:
75
+ { "name": "first", "another_value": "second" }
76
+
77
+ Now we get a 200 back because we succeeded. Succeeded at what? Let's see:
78
+
79
+ GET http://localhost:7357/things
80
+
81
+ That 200s and returns this:
82
+
83
+ {
84
+ "0": {
85
+ "name": "first",
86
+ "another_value": "second"
87
+ }
88
+ }
89
+
90
+ You can also retrieve by index.
91
+
92
+ GET http://localhost:7357/things/0
93
+ returns:
94
+ {
95
+ "name": "first",
96
+ "another_value": "second"
97
+ }
98
+
99
+ In general, this will behave like an in-memory CRUD store. That starts and stops and restarts. So much for basics . . .
100
+
101
+ $ bin/scc.rb stop
102
+
103
+
104
+ #### Controlling the Data
105
+
106
+ There is a control endpoint that does more for us.
107
+
108
+
109
+ ##### Running
110
+
111
+ Yes, this returns { "status": "running" } if running:
112
+
113
+ GET http://localhost:7537/control
114
+
115
+
116
+ ##### Data
117
+
118
+ You can look at or change all of the data either with the /control/data endpoint or with the command line tool's data command.
119
+
120
+ If you want to have the server not respond do this:
121
+ 1. get data (optionally save somewhere)
122
+ 2. stop service
123
+ 3. run query-to-fail against service
124
+ 4. start service
125
+ 5. restore data
126
+
127
+
128
+ ##### Version Numbers Et Al.
129
+
130
+ To support urls with things like version numbers the service already supports urls like this:
131
+
132
+ http://localhost/<ADDITONAL_STRING>/collection/1
133
+
134
+ Where <ADDITIONAL_STRING> is any string. That means GETting and POSTing these is the same:
135
+
136
+ http://localhost/collection/0
137
+ http://localhost/FOO/collection/0
138
+ http://localhost/v1/BAR/BAZ/collection/0
139
+
140
+
141
+ ## TODO
142
+
143
+ I should still add this functionality:
144
+ - return non-REST data (possibly non-HTTP)
145
+ - GraphQL? Some subset of it?
146
+ - Others? WSDL?
147
+ - Client bindings? POROs?
148
+
51
149
 
52
150
  ## Development
53
151
 
@@ -55,18 +153,19 @@ After checking out the repo, run `bundle` to install dependencies. Then, run `bu
55
153
 
56
154
  To install this gem onto your local machine, run
57
155
 
58
- bundle exec rake install
156
+ $ bundle exec rake install
59
157
 
60
158
  To release a new version, update the version number in `version.rb` like so
61
159
 
62
- bundle exec gem bump -v [major|minor|patch|pre|release]
160
+ $ bundle exec gem bump -v [major|minor|patch|pre|release]
63
161
 
64
162
  and then run
65
163
 
66
- bundle exec rake release
164
+ $ bundle exec rake release
67
165
 
68
166
  which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
69
167
 
168
+
70
169
  ## Contributing
71
170
 
72
171
  Bug reports and pull requests are welcome on GitHub at https://github.com/drewcoo/doppelserver.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
-
2
+ $LOAD_PATH.unshift File.expand_path 'lib', __dir__
3
3
  require 'bundler/setup'
4
- require 'fake_server'
4
+ require 'doppelserver'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path '../lib', __dir__
3
+ require 'rubygems'
4
+ require 'doppelserver'
5
+ require 'thor'
6
+
7
+ #
8
+ # A command line tool to tell the Doppelserver service controller
9
+ # what to do.
10
+ #
11
+ class ServiceControllerConsole < Thor
12
+ map %w[/? /h] => :help
13
+ map 'up' => :start
14
+ map 'down' => :stop
15
+
16
+ DEFAULT_PORT = '7357'.freeze
17
+ class_option :port, default: DEFAULT_PORT, aliases: '-p',
18
+ desc: 'port service runs on'
19
+
20
+ desc 'helper: bail_out!', 'abort with a message', hide: true
21
+ def bail_out!
22
+ message = running? ? 'already' : 'not'
23
+ abort "ERROR #{message} running on port #{options.port}"
24
+ end
25
+
26
+ desc 'helper: running?', 'Bool am I running?', hide: true
27
+ def running?
28
+ # memoize this so that we don't call running? twice
29
+ # when we 'bail_out! if runnning?' and we exit this tool after each call
30
+ # so the memoization doesn't hurt x-call
31
+ #
32
+ # I used this, so left it commented in case I need it later:
33
+ # puts defined?(@running) ? 'defined' : 'not defined'
34
+ @running ||= service_controller.running?
35
+ end
36
+
37
+ desc 'helper: service controller', 'tell the service what to do', hide: true
38
+ def service_controller
39
+ @service_controller ||= ServiceController.new(options.port)
40
+ end
41
+
42
+ #
43
+ # Also hide this method. It's the one that starts the server
44
+ # as the current process. The start method starts a new process
45
+ # invoking this.
46
+ #
47
+ desc 'server', 'do not call directly', hide: true
48
+ def server
49
+ service_controller.serve
50
+ end
51
+
52
+ desc 'start', 'start server'
53
+ method_option :data, aliases: '-d',
54
+ desc: 'text JSON data - remember to escape quotes'
55
+ def start
56
+ bail_out! if running?
57
+ service_controller.start
58
+ set unless options.data.nil?
59
+ end
60
+
61
+ desc 'stop', 'stop server'
62
+ method_option :data, aliases: '-d',
63
+ desc: 'if passed, dumps data to console'
64
+ def stop
65
+ bail_out! unless running?
66
+ data if options.data
67
+ service_controller.stop
68
+ end
69
+
70
+ desc 'restart', 'restart server, dumping all data'
71
+ def restart
72
+ bail_out! unless running?
73
+ service_controller.restart
74
+ end
75
+
76
+ # Bad data zeroes everything out. Fix that or just doc it in help?
77
+ desc 'data', 'send/retreive all data'
78
+ method_option :set, aliases: '-s',
79
+ desc: '-s <data> to send JSON; no args to get from server'
80
+ def data
81
+ bail_out! unless running?
82
+ if options.set
83
+ service_controller.set(options.set)
84
+ else
85
+ STDOUT.puts service_controller.get.gsub('"', '\"')
86
+ end
87
+ end
88
+ end
89
+
90
+ ServiceControllerConsole.start
@@ -33,8 +33,8 @@ Gem::Specification.new do |spec|
33
33
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
34
34
  f.match(%r{^(test|spec|features)/})
35
35
  end
36
- spec.bindir = 'exe'
37
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.bindir = 'bin'
37
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
38
38
  spec.require_paths = ['lib']
39
39
 
40
40
  spec.add_development_dependency 'activesupport', '~> 5.0'
@@ -1,4 +1,3 @@
1
1
  require 'doppelserver/version'
2
- require 'sinatra'
3
- # require 'sinatra/contrib'
4
2
  require 'doppelserver/base_server'
3
+ require 'doppelserver/service_controller'
@@ -1,35 +1,11 @@
1
1
  require 'sinatra'
2
- require 'sinatra/json'
3
- # require File.expand_path '../../lib/doppelserver/base_server.rb', __FILE__
4
- require_relative 'data'
2
+ require 'doppelserver/data'
3
+ # Most of the BaseServer class is in the routes below:
4
+ require 'doppelserver/routes/control'
5
+ require 'doppelserver/routes/rest'
5
6
 
6
7
  module Doppelserver
7
- ENFORCE_PLURALS = true
8
-
9
8
  class BaseServer < Sinatra::Base
10
- require 'active_support' # for singularization/pluralization
11
-
12
- #
13
- # Determines if
14
- # word:: an input word
15
- # is singular.
16
- # Retutns truthy.
17
- #
18
- def singular?(word)
19
- ActiveSupport::Inflector.singularize(word) == word
20
- end
21
-
22
- #
23
- # If ENFORCE_PLURALS is true (default == true)
24
- # This enforces plural names for all collections
25
- # and returns 403 otherwise.
26
- # word:: collection name
27
- #
28
- def enforce_plural(word)
29
- return unless ENFORCE_PLURALS
30
- halt 403, 'only plural collection names allowed' if singular?(word)
31
- end
32
-
33
9
  #
34
10
  # Hold all of the data internally in the @@data.
35
11
  # It's the Data class, which holds a hash of data and
@@ -39,118 +15,6 @@ module Doppelserver
39
15
  @@data = Data.new
40
16
  end
41
17
 
42
- #
43
- # All meta-operations should happen through a /control endpoint.
44
- #
45
- # Deletes the internal data by clearing the @@data hash.
46
- #
47
- delete '/control/data' do
48
- @@data.clear
49
- end
50
-
51
- #
52
- # TODO:: Define this.
53
- # Maybe make it a list of collections?
54
- # Or all collections with all their data?
55
- #
56
- # For now, it returns a placeholder string.
57
- #
58
- get '/' do
59
- 'here i am' # TODO: Change this.
60
- end
61
-
62
- #
63
- # Gets the data at:
64
- # endpoint:: the collection
65
- # id:: the instance identifier
66
- # Or 404s when not found.
67
- #
68
- get '/:endpoint/:id' do
69
- collection, id = params['endpoint'], params['id']
70
- enforce_plural(collection)
71
- halt 404 unless @@data.collection?(collection)
72
- result = @@data.get_value(collection, id)
73
- halt 404 if result.nil?
74
- json(result)
75
- end
76
-
77
- #
78
- # Returns the data at:
79
- # endpoint:: collection name
80
- # This is all of the data in that collection
81
- # or 404 if no endpoint.
82
- #
83
- get '/:endpoint' do
84
- collection = params[:endpoint]
85
- enforce_plural(collection)
86
- halt 404 unless @@data.collection?(collection)
87
- json(@@data.get_collection(collection))
88
- end
89
-
90
- #
91
- # No PUT, POST.
92
- # Adds data to collection:
93
- # endpoint:: is the name of the collection.
94
- # If it doesn't exist, add new collection.
95
- # If it does exist replaces (???) data.
96
- # Also 403 on no data passed.
97
- #
98
- post '/:endpoint' do
99
- request.body.rewind
100
- collection = params['endpoint']
101
- enforce_plural(collection)
102
- raw_data = request.body.read
103
- halt 403, 'NO DATA' if raw_data.empty?
104
- data = begin
105
- JSON.parse(raw_data)
106
- rescue
107
- halt 403, "BAD DATA:\n#{raw_data}"
108
- end
109
- json(id: @@data.add(collection, data))
110
- end
111
-
112
- #
113
- # No PUT, POST.
114
- # Add data with the id to the collection.
115
- # endpoint:: collection name
116
- # id:: unique identifier for the data
117
- # Adds a collection if doesn't exist.
118
- # Adds id and data at it if id doesn't exist.
119
- # Otherwise, replaces data. Unless no data, then 403.
120
- #
121
- post '/:endpoint/:id' do
122
- request.body.rewind
123
- collection, id = params['endpoint'], params['id']
124
- enforce_plural(collection)
125
- halt 403 unless @@data.collection_key?(collection, id)
126
- raw_data = request.body.read
127
- halt 403, 'NO DATA' if raw_data.empty?
128
- data = begin
129
- JSON.parse(raw_data)
130
- rescue
131
- halt 403, "BAD DATA:\n#{raw_data}"
132
- end
133
- halt 403 unless @@data.update?(collection, id, data)
134
- end
135
-
136
- #
137
- # Delete the data from
138
- # endpoint:: collection name
139
- # id:: with unique identifier
140
- # Or 404 if one of those isn't found.
141
- #
142
- delete '/:endpoint/:id' do
143
- request.body.rewind
144
- collection, id = params['endpoint'], params['id']
145
- enforce_plural(collection)
146
- halt 404 unless @@data.collection?(collection)
147
- if @@data.delete(collection, id)
148
- status 200
149
- else
150
- halt 404
151
- end
152
- end
153
-
154
18
  run! if app_file == $PROGRAM_NAME
155
19
  end
156
20
  end
@@ -4,7 +4,12 @@ module Doppelserver
4
4
  #
5
5
  class Data
6
6
  #
7
- # Initiallizes (cleared) all internal data.
7
+ # @data will be COLLECTION : { 0: { HASH }, 1: { HASH }, ... }
8
+ # with the number autoincrementing by default
9
+ # @next_keys is the hash of COLLECTION names and the next auto-
10
+ # incrementing value
11
+ #
12
+ # And now it's all empty.
8
13
  #
9
14
  def initialize
10
15
  @data = {}
@@ -34,6 +39,18 @@ module Doppelserver
34
39
  collection?(collection) && @data[collection].key?(key)
35
40
  end
36
41
 
42
+ def hash
43
+ { 'data' => @data,
44
+ 'next_keys' => @next_keys }
45
+ end
46
+ alias export hash
47
+
48
+ def hash=(hash)
49
+ @data = hash['data']
50
+ @next_keys = hash['next_keys']
51
+ end
52
+ alias import hash=
53
+
37
54
  #
38
55
  # Returns collection named parameter name.
39
56
  #
@@ -0,0 +1,59 @@
1
+ require 'sinatra'
2
+ require 'sinatra/json'
3
+ require 'doppelserver/routes/helpers/request_parse_helper'
4
+
5
+ module Doppelserver
6
+ #
7
+ # The control routes for BaseServer
8
+ #
9
+ class BaseServer < Sinatra::Base
10
+ include RequestParseHelper
11
+ #
12
+ # All meta-operations should happen through a /control endpoint.
13
+ #
14
+ # DELETEs the internal data by clearing the @@data hash.
15
+ #
16
+ delete '/control/data' do
17
+ @@data.clear
18
+ end
19
+
20
+ #
21
+ # GET all of the data.
22
+ #
23
+ get '/control/data' do
24
+ json(@@data.export)
25
+ end
26
+
27
+ #
28
+ # Replace all of the data.
29
+ # GET the data, stop the service, restart the service, and POST the data
30
+ # and we have the service in the same state as is was.
31
+ #
32
+ post '/control/data' do
33
+ request.body.rewind
34
+ @@data.hash = read_post_data(request.body)
35
+ status 200
36
+ end
37
+
38
+ #
39
+ # TODO:: Define this.
40
+ # Maybe make it a list of collections?
41
+ # Or all collections with all their data?
42
+ #
43
+ # For now, it returns a placeholder string.
44
+ #
45
+ get '/control' do
46
+ json('status': 'running')
47
+ end
48
+
49
+ #
50
+ # DELETE on the /control endpoint to stop the service
51
+ # and Kernel.exit! stops immedately, skipping even at_exit stuff.
52
+ #
53
+ delete '/control' do
54
+ # Save state first or count on caller to prep for the service
55
+ # losing all data?
56
+ exit!
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Capture helper - regexen.
3
+ #
4
+ module CaptureHelper
5
+ # Seems evil but it works. :-(
6
+ # these do pattern matching.
7
+ # They parse out what they say they do while allowing ANY prefix.
8
+ # So http://localhost/v3/myserver/route/id works just like
9
+ # http://localhost/route/id
10
+ #
11
+ # Because this seems wrong, I'm leaving it notably ugly so that I'll notice
12
+ # and maybe improve it later.
13
+ #
14
+ MATCH_NONSLASH = /([^\/]+)/
15
+ MATCH_NUMBER = /([\d]+)/
16
+ # These start with optional everythingm followed bu slash-separated matches
17
+ COLLECTION_ONLY = /.*\/#{MATCH_NONSLASH}\/?/ # possible trailing slash
18
+ COLLECTION_AND_ID = /.*\/#{MATCH_NONSLASH}\/#{MATCH_NUMBER}/
19
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Like the name suggests, helper methods for pluralization.
3
+ #
4
+ module PluralizeHelper
5
+ require 'active_support'
6
+
7
+ ENFORCE_PLURALS = true
8
+
9
+ #
10
+ # Determines if
11
+ # word:: an input word
12
+ # is singular.
13
+ # Retutns truthy.
14
+ #
15
+ def singular?(word)
16
+ ActiveSupport::Inflector.singularize(word) == word
17
+ end
18
+
19
+ #
20
+ # If ENFORCE_PLURALS is true (default == true)
21
+ # This enforces plural names for all collections
22
+ # and returns 403 otherwise.
23
+ # word:: collection name
24
+ #
25
+ def enforce_plural(word)
26
+ # Mixing in the contol exclusion is strange here.
27
+ # This can still coexist with a /controls endpoint but may be confusing.
28
+ # I don't want to think about the possibility that we don't have plural
29
+ # collections and happen to have a collision with a control collection yet.
30
+ if word != 'control' && ENFORCE_PLURALS && singular?(word)
31
+ halt 403, 'only plural collection names allowed'
32
+ end
33
+ word
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ require 'doppelserver/routes/helpers/pluralize_helper'
2
+ #
3
+ # Helper methods for web request parsing.
4
+ #
5
+ module RequestParseHelper
6
+ include PluralizeHelper
7
+ #
8
+ # Parses parameters and checks that collections are plural if needed.
9
+ #
10
+ def parse_params(params)
11
+ collection, id = params['captures']
12
+ enforce_plural(collection)
13
+ id ? [collection, id] : collection
14
+ end
15
+
16
+ #
17
+ # Also for non-POST, callers ask to check the collection against
18
+ # known collections and 404 if asking for an unknown one
19
+ #
20
+ def check_exists(collection, data)
21
+ halt 404 unless data.collection?(collection)
22
+ end
23
+
24
+ #
25
+ # Takes a request body and returns post data.
26
+ # Unless is errors out because of no data or a parse error.
27
+ #
28
+ def read_post_data(body)
29
+ raw_data = body.read
30
+ halt 403, 'NO DATA' if raw_data.empty?
31
+ begin
32
+ JSON.parse(raw_data)
33
+ rescue JSON::ParserError
34
+ halt 403, "BAD DATA:\n#{raw_data}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,89 @@
1
+ require 'sinatra'
2
+ require 'sinatra/json'
3
+ require 'doppelserver/routes/helpers/capture_helper'
4
+ require 'doppelserver/routes/helpers/request_parse_helper'
5
+
6
+ module Doppelserver
7
+ #
8
+ # All of the REST routes for BaseServer are here.
9
+ #
10
+ class BaseServer < Sinatra::Base
11
+ include CaptureHelper
12
+ include RequestParseHelper
13
+
14
+ #
15
+ # Gets the data at:
16
+ # endpoint:: the collection
17
+ # id:: the instance identifier
18
+ # Or 404s when not found.
19
+ #
20
+ get COLLECTION_AND_ID do
21
+ collection, id = parse_params(params)
22
+ check_exists(collection, @@data)
23
+ result = @@data.get_value(collection, id)
24
+ halt 404 if result.nil?
25
+ json(result)
26
+ end
27
+
28
+ #
29
+ # Returns the data at:
30
+ # endpoint:: collection name
31
+ # This is all of the data in that collection
32
+ # or 404 if no endpoint.
33
+ #
34
+ get COLLECTION_ONLY do
35
+ collection = parse_params(params)
36
+ check_exists(collection, @@data)
37
+ json(@@data.get_collection(collection))
38
+ end
39
+
40
+ #
41
+ # No PUT, POST.
42
+ # Add data with the id to the collection.
43
+ # endpoint:: collection name
44
+ # id:: unique identifier for the data
45
+ # Adds a collection if doesn't exist.
46
+ # Adds id and data at it if id doesn't exist.
47
+ # Otherwise, replaces data. Unless no data, then 403.
48
+ #
49
+ post COLLECTION_AND_ID do
50
+ request.body.rewind
51
+ collection, id = parse_params(params)
52
+ halt 403 unless @@data.collection_key?(collection, id)
53
+ data = read_post_data(request.body)
54
+ halt 403 unless @@data.update?(collection, id, data)
55
+ end
56
+
57
+ #
58
+ # No PUT, POST.
59
+ # Adds data to collection:
60
+ # endpoint:: is the name of the collection.
61
+ # If it doesn't exist, add new collection.
62
+ # If it does exist replaces (???) data.
63
+ # Also 403 on no data passed.
64
+ #
65
+ post COLLECTION_ONLY do
66
+ request.body.rewind
67
+ collection = parse_params(params)
68
+ data = read_post_data(request.body)
69
+ json(id: @@data.add(collection, data))
70
+ end
71
+
72
+ #
73
+ # Delete the data from
74
+ # endpoint:: collection name
75
+ # id:: with unique identifier
76
+ # Or 404 if one of those isn't found.
77
+ #
78
+ delete COLLECTION_AND_ID do
79
+ request.body.rewind
80
+ collection, id = parse_params(params)
81
+ check_exists(collection, @@data)
82
+ if @@data.delete(collection, id)
83
+ status 200
84
+ else
85
+ halt 404
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,75 @@
1
+ $LOAD_PATH.unshift File.expand_path('..', __dir__)
2
+ require 'doppelserver/base_server'
3
+ require 'faraday'
4
+ require 'json'
5
+
6
+ #
7
+ # Contains the commands to tell the service what to do.
8
+ # All of the command line parsing and such is in a /bin/ that I haven't
9
+ # decided what to name yet.
10
+ #
11
+ class ServiceController
12
+ SERVER = 'http://127.0.0.1'
13
+
14
+ attr_reader :port
15
+
16
+ def initialize(port)
17
+ @port = port
18
+ end
19
+
20
+ def start
21
+ command = "ruby #{__FILE__} server #{@port}"
22
+ command = if ENV['OS'] == 'Windows_NT'
23
+ "start \"doppelserver\" cmd /c #{command}"
24
+ else
25
+ "nohup #{command} &"
26
+ end
27
+ system command
28
+ sleep 0.1 until running?
29
+ end
30
+
31
+ def serve
32
+ system "title doppelserver - port #{@port}" if ENV['OS'] == 'Windows_NT'
33
+ server = Doppelserver::BaseServer
34
+ server.port = @port
35
+ server.run!
36
+ end
37
+
38
+ # rubocop:disable Lint/HandleExceptions
39
+ def stop
40
+ return unless running?
41
+ Faraday.delete "#{SERVER}:#{@port}/control"
42
+ rescue Faraday::ConnectionFailed
43
+ # return 0
44
+ end
45
+ # rubocop:enable Lint/HandleExceptions
46
+
47
+ def restart
48
+ stop
49
+ start
50
+ end
51
+
52
+ def running?
53
+ response = Faraday.get "#{SERVER}:#{@port}/control"
54
+ JSON.parse(response.body)['status'] == 'running'
55
+ rescue Faraday::ConnectionFailed
56
+ false
57
+ end
58
+
59
+ def get
60
+ response = Faraday.get "#{SERVER}:#{@port}/control/data"
61
+ response.body
62
+ end
63
+
64
+ def set(data)
65
+ Faraday.post "#{SERVER}:#{@port}/control/data", data
66
+ end
67
+ end
68
+
69
+ #
70
+ # If this, then turn this process into a runnning service.
71
+ #
72
+ if $PROGRAM_NAME == __FILE__ && ARGV[0] == 'server'
73
+ server = ServiceController.new(ARGV[1])
74
+ server.serve
75
+ end
@@ -1,3 +1,3 @@
1
1
  module Doppelserver
2
- VERSION = '0.4.9'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doppelserver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.9
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drew Cooper
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-08 00:00:00.000000000 Z
11
+ date: 2018-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -212,7 +212,10 @@ description: |-
212
212
  Unlike mocks and stubs, fake services are running processes that pretend to function as real ones would. This enables testing the software under test with more complete control of the surfaces it talks to (other services, faked). Beyond that, it makes application- and (http)protocol-level fault injection easy.
213
213
  email:
214
214
  - drewcoo@gmail.com
215
- executables: []
215
+ executables:
216
+ - console
217
+ - scc.rb
218
+ - setup
216
219
  extensions: []
217
220
  extra_rdoc_files: []
218
221
  files:
@@ -229,12 +232,18 @@ files:
229
232
  - Rakefile
230
233
  - appveyor.yml
231
234
  - bin/console
232
- - bin/server
235
+ - bin/scc.rb
233
236
  - bin/setup
234
237
  - doppelserver.gemspec
235
238
  - lib/doppelserver.rb
236
239
  - lib/doppelserver/base_server.rb
237
240
  - lib/doppelserver/data.rb
241
+ - lib/doppelserver/routes/control.rb
242
+ - lib/doppelserver/routes/helpers/capture_helper.rb
243
+ - lib/doppelserver/routes/helpers/pluralize_helper.rb
244
+ - lib/doppelserver/routes/helpers/request_parse_helper.rb
245
+ - lib/doppelserver/routes/rest.rb
246
+ - lib/doppelserver/service_controller.rb
238
247
  - lib/doppelserver/version.rb
239
248
  homepage: https://github.com/drewcoo/doppelserver
240
249
  licenses:
data/bin/server DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require 'doppelserver'
3
- Doppelserver::BaseServer.run!