siringa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +169 -0
  4. data/Rakefile +39 -0
  5. data/app/controllers/siringa/application_controller.rb +4 -0
  6. data/app/controllers/siringa/siringa_controller.rb +49 -0
  7. data/app/helpers/siringa/application_helper.rb +4 -0
  8. data/config/routes.rb +5 -0
  9. data/lib/generators/siringa/install_generator.rb +18 -0
  10. data/lib/generators/templates/siringa_initializer.rb +9 -0
  11. data/lib/siringa.rb +4 -0
  12. data/lib/siringa/configuration.rb +26 -0
  13. data/lib/siringa/definitions.rb +38 -0
  14. data/lib/siringa/dumps.rb +66 -0
  15. data/lib/siringa/engine.rb +5 -0
  16. data/lib/siringa/version.rb +3 -0
  17. data/lib/tasks/siringa_tasks.rake +11 -0
  18. data/test/controllers/siringa_controller_test.rb +23 -0
  19. data/test/dummy/Rakefile +7 -0
  20. data/test/dummy/app/assets/javascripts/application.js +9 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  22. data/test/dummy/app/controllers/application_controller.rb +3 -0
  23. data/test/dummy/app/helpers/application_helper.rb +2 -0
  24. data/test/dummy/app/models/user.rb +3 -0
  25. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/test/dummy/config.ru +4 -0
  27. data/test/dummy/config/application.rb +45 -0
  28. data/test/dummy/config/boot.rb +10 -0
  29. data/test/dummy/config/database.yml +25 -0
  30. data/test/dummy/config/environment.rb +5 -0
  31. data/test/dummy/config/environments/development.rb +30 -0
  32. data/test/dummy/config/environments/production.rb +60 -0
  33. data/test/dummy/config/environments/test.rb +39 -0
  34. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/test/dummy/config/initializers/inflections.rb +10 -0
  36. data/test/dummy/config/initializers/mime_types.rb +5 -0
  37. data/test/dummy/config/initializers/secret_token.rb +7 -0
  38. data/test/dummy/config/initializers/session_store.rb +8 -0
  39. data/test/dummy/config/initializers/siringa.rb +5 -0
  40. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/test/dummy/config/locales/en.yml +5 -0
  42. data/test/dummy/config/routes.rb +4 -0
  43. data/test/dummy/db/development.sqlite3 +0 -0
  44. data/test/dummy/db/migrate/20130927161308_create_user.rb +7 -0
  45. data/test/dummy/db/schema.rb +20 -0
  46. data/test/dummy/db/test.sqlite3 +0 -0
  47. data/test/dummy/log/development.log +1069 -0
  48. data/test/dummy/log/test.log +1471 -0
  49. data/test/dummy/public/404.html +26 -0
  50. data/test/dummy/public/422.html +26 -0
  51. data/test/dummy/public/500.html +26 -0
  52. data/test/dummy/public/favicon.ico +0 -0
  53. data/test/dummy/script/rails +6 -0
  54. data/test/dummy/test/factories.rb +7 -0
  55. data/test/dummy/test/siringa/definitions.rb +6 -0
  56. data/test/dummy/tmp/dumps/db_20130927191400.dump +0 -0
  57. data/test/integration/navigation_test.rb +10 -0
  58. data/test/siringa_test.rb +7 -0
  59. data/test/test_helper.rb +10 -0
  60. metadata +158 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGIxNzZkYjA3ZmM5MmU2MTM1OTVmOTNmM2NiNDhmNmI2M2UxMzgzMw==
5
+ data.tar.gz: !binary |-
6
+ NWZiYWE3MTIzMjI1NTE0YzJjZjhiYzJmNGRiMzlhYjQyOTA4MGZjMw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YjM1NDczYWU5N2JjMjA1YTY0MTIwMjQzZjljMjZlNzBkMDQ3NjYxZDYwOGQw
10
+ ZTBlZTc2ZDRiMjhkNTI1N2QwNmY2YWYwNzQ4NzVlYTliMmQxNmJjNThmNjI2
11
+ YmM1MGRlMjQ0MWEyMTIzN2E2OTBjYWIwNDU1YTU3MTViOWRkZmM=
12
+ data.tar.gz: !binary |-
13
+ NDdmYzI2ZmRjOGQ2MWRiMDE0NDc2MGZiYTA5MTEyNWIxNGIxMzc1MTgwZjg0
14
+ NmEyMzNhNzgyMmE3ZjcyZmJmOWIzNWYwYTE0Y2JmZTUyYTRmNWRlNTcyOTJk
15
+ MmVhYmU3YjA1MmYwNDNhYzdjOTMxMTcwMTk2NzM0ZGNmNmY0MDQ=
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Enrico Stano
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,169 @@
1
+ # Siringa :syringe:
2
+ This gem was born working on pure client acceptance testing in [Teambox](http://www.teambox.com).
3
+ ## The problem
4
+
5
+ You have a Rails based API and you need to write some acceptance tests using your Javascript/iOS/Android/whatever client.
6
+
7
+ Probably you'll have three basic needs:
8
+
9
+ 1. Pre-populate the DB with some (maybe massive) data (a.k.a. seed data)
10
+ 2. Your seed data files have to be easily maintainable, you don't want to touch them too much when a model change and so on
11
+ 3. Populate your DB while executing some specific acceptance test and then clean up the DB restoring the initial situation
12
+
13
+ ## The solution
14
+
15
+ 1. Write your seed data and use Siringa's rake recipes to pre-populate the DB just after you deploy your API
16
+ 2. I suggest to use factories to create your seed data in order to take advantage of factories maybe you've already written for unit tests. But you could use whatever other way to keep your seed data maintainable and you still could use Siringa
17
+ 3. Use Siringa's extra API endpoints in order to dump your inital DB situation, load some factories and then restore the initial status when you're done
18
+
19
+ **Why you don't just use your app's API to do the same thing, maybe storing JSON/XML objects in some fixture?**
20
+
21
+ Well, because mantaining those huge JSON/XML fixture files will be a nightmare.
22
+
23
+ ## Install Siringa
24
+
25
+ Add the following to your `Gemfile`:
26
+ ```ruby
27
+ group :test do
28
+ gem 'siringa'
29
+ end
30
+ ```
31
+ For security reasons, I suggest to add it under the `test` group. In that way you'll have Siringa available only running the app in `test` environment.
32
+
33
+ Run the bundle command to install it.
34
+
35
+ Run the generator:
36
+ ```console
37
+ rails generate siringa:install
38
+ ```
39
+ This will add the Siringa initializer in `config/initializers/siringa.rb` and create an empty directory in `tmp/dumps`.
40
+
41
+ Add the following to your `config/routes.rb`:
42
+ ```ruby
43
+ mount Siringa::Engine, :at => '/siringa'
44
+ ```
45
+ Make sure that you add it in the correct environment. For instance if you want it available only in `test` environment put it inside a condition statement like `if Rails.env.test? .. end`.
46
+ ## Create definitions
47
+
48
+ In order to create a new Siringa definition just create a new file in the `test/siringa` directory like the following:
49
+
50
+ ```ruby
51
+ # test/siringa/definitions.rb
52
+ require 'siringa'
53
+ require File.expand_path('../../factories', __FILE__)
54
+
55
+ Siringa.add_definition :initial do
56
+ FactoryGirl.create :user,
57
+ name: 'Jesse Pinkman'
58
+ end
59
+
60
+ Siringa.add_definition :specific do
61
+ FactoryGirl.create :user,
62
+ name: 'Hank Schrader'
63
+ FactoryGirl.create :project,
64
+ name: 'Pollos Hermanos'
65
+ end
66
+ ```
67
+
68
+ I'm using [FactoryGirl](https://github.com/thoughtbot/factory_girl) gem here but in a definition you could write whatever kind of Ruby code and Siringa will execute it when you call the definition.
69
+
70
+ ## Load definitions
71
+ Now that we've created our definitions we could use them in our two cases:
72
+ ### As seed data
73
+ You could just load the `initial` definition running the following rake recipe after your deploy:
74
+ ```console
75
+ rake siringa:load
76
+ ```
77
+ `initial` is the default definition this Rake task recipe will try to load, if you want to load another definition instead you could pass its name as argument:
78
+ ```console
79
+ rake 'siringa:load[another_definition]'
80
+ ```
81
+ This will load a definition named `:another_definition`. Please note that if you use Zsh shell you'll need to use quotes, more info [here](http://robots.thoughtbot.com/post/18129303042/how-to-use-arguments-in-a-rake-task).
82
+
83
+ If you want to force a Rails environment you could just run the Sriringa recipe specifing the `RAILS_ENV` environmental variable:
84
+ ```console
85
+ RAILS_ENV=development rake siringa:load
86
+ ```
87
+
88
+ ### During an acceptance test
89
+ As you can see running `rake routes`, Siringa added 3 new routes to your API:
90
+ ```console
91
+ Routes for Siringa::Engine:
92
+ POST /load/:definition(.:format) siringa/siringa#load
93
+ dump POST /dump(.:format) siringa/siringa#dump
94
+ restore POST /restore(.:format) siringa/siringa#restore
95
+ ```
96
+
97
+ The workflow I propose here is:
98
+
99
+ 1. Create a dump of your DB performing a `POST` request to `YOURHOST/siringa/dump`
100
+
101
+ This will create a `.dump` file in the `tmp/dumps` directory created during the install process. You could create as many dump files as you want but Siringa will keep only the latest 5 dumps created.
102
+
103
+ 2. Load a Siringa definition performing a `POST` request to `YOURHOST/siringa/load/specific`
104
+
105
+ This will run the code defined in a definition named `specific` on the server.
106
+
107
+ 3. Go ahead with your acceptance test
108
+
109
+ 4. Restore the DB status performing a `POST` request to `YOURHOST/siringa/restore`
110
+
111
+ This will bring back your DB at the initial status using the dump file you created in step 1.
112
+
113
+ Please note that the `Siringa::restore` method only use the latest dump file created, older dump files in the `tmp/dumps` folder will be ignored.
114
+
115
+ ## Customize Siringa
116
+ You could customize Siringa changing the configuration stored in the initializer:
117
+ ```ruby
118
+ # config/initializers/siringa.rb
119
+ Siringa.configure do |config|
120
+ # customize the path where the definitions are stored
121
+ config.definitions_path = 'test/siringa'
122
+ # customize the path where the DB dumps are stored
123
+ config.dumps_path = 'tmp/dumps'
124
+ end
125
+
126
+ Siringa.load_definitions
127
+ ```
128
+ ## Acceptance test example
129
+ Just to get the idea, the following script will show you how to use Siringa in a acceptance test using Selenium, [rest-client](https://github.com/rest-client/rest-client) and RSpec:
130
+ ```ruby
131
+ require 'selenium-webdriver'dd
132
+ require 'rspec'
133
+ require 'rest_client'
134
+
135
+ describe "Acceptance test using Siringa" do
136
+ before(:all) do
137
+ # Dump your DB
138
+ RestClient.post 'YOURHOST/siringa/dump'
139
+ end
140
+
141
+ before(:each) do
142
+ @driver = Selenium::WebDriver.for :firefox
143
+ end
144
+
145
+ after(:each) do
146
+ @driver.quit
147
+ end
148
+
149
+ it "Load a definition and test something" do
150
+ # Load a definition named 'specific'
151
+ RestClient.post 'YOURHOST/siringa/load', { :definition => :specific }
152
+
153
+ # Here goes your test
154
+
155
+ # Restore your DB
156
+ RestClient.post 'YOURHOST/siringa/restore'
157
+ end
158
+ end
159
+ ```
160
+ ## To Do
161
+ * Write more tests
162
+ * Add Postgres adaptor compatibility for dumps and restores
163
+ * Add more customizations
164
+
165
+ ## How to collaborate
166
+ * Fork the repo and send a pull request, thanks!
167
+
168
+ # Licence
169
+ MIT-LICENSE 2013 Enrico Stano
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Siringa'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+ Bundler::GemHelper.install_tasks
28
+
29
+ require 'rake/testtask'
30
+
31
+ Rake::TestTask.new(:test) do |t|
32
+ t.libs << 'lib'
33
+ t.libs << 'test'
34
+ t.pattern = 'test/**/*_test.rb'
35
+ t.verbose = false
36
+ end
37
+
38
+
39
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ module Siringa
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,49 @@
1
+ module Siringa
2
+ class SiringaController < ApplicationController
3
+
4
+ def load
5
+ Siringa.load_definition(params['definition'].to_sym)
6
+ resp = { :text => "Definition #{params['definition']} loaded.", :status => :created }
7
+ rescue ArgumentError => exception
8
+ resp = { :text => exception.to_s, :status => :method_not_allowed }
9
+ rescue => exception
10
+ resp = { :text => exception.to_s, :status => :internal_server_error }
11
+ ensure
12
+ render resp
13
+ end
14
+
15
+ def dump
16
+ result = Siringa.dump_to(Siringa.dump_file_name)
17
+ if result[:success]
18
+ Siringa.keep_five_dumps(Siringa.ordered_dumps)
19
+ resp = { :text => "DB dumped at #{result[:file_name]}",
20
+ :status => :created }
21
+ else
22
+ resp = { :text => "DB dump FAILED!\nError:\n#{result[:output]}",
23
+ :status => :internal_server_error }
24
+ end
25
+
26
+ render resp
27
+ end
28
+
29
+ def restore
30
+ last_dump = Siringa.ordered_dumps.last
31
+ if last_dump
32
+ result = Siringa.restore_from(last_dump)
33
+ if result[:success]
34
+ resp = { :text => "DB restored from #{result[:file_name]}",
35
+ :status => :accepted }
36
+ else
37
+ resp = { :text => "DB restore FAILED!\nError:\n#{result[:output]}",
38
+ :status => :internal_server_error }
39
+ end
40
+ else
41
+ resp = { :text => "DB restore FAILED!\nThere is no dump to restore from.",
42
+ :status => :method_not_allowed }
43
+ end
44
+
45
+ render resp
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ module Siringa
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ Siringa::Engine.routes.draw do
2
+ post "load/:definition", :to => "siringa#load"
3
+ post "dump", :to => "siringa#dump"
4
+ post "restore", :to => "siringa#restore"
5
+ end
@@ -0,0 +1,18 @@
1
+ module Siringa
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+ desc "Creates Siringa initializer for your application"
6
+
7
+ def copy_initializer
8
+ template "siringa_initializer.rb", "config/initializers/siringa.rb"
9
+
10
+ puts "Install complete!"
11
+ end
12
+
13
+ def create_dumps_folder
14
+ empty_directory "tmp/dumps"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ # config/initializers/siringa.rb
2
+ Siringa.configure do |config|
3
+ # customize the path where the definitions are stored
4
+ # config.definitions_path = 'test/siringa'
5
+ # customize the path where the dumps are stored
6
+ # config.dumps_path = 'tmp/dumps'
7
+ end
8
+
9
+ Siringa.load_definitions
@@ -0,0 +1,4 @@
1
+ require "siringa/engine"
2
+ require "siringa/configuration"
3
+ require "siringa/definitions"
4
+ require "siringa/dumps"
@@ -0,0 +1,26 @@
1
+ module Siringa
2
+
3
+ @definitions = {}
4
+
5
+ class << self
6
+ attr_accessor :configuration
7
+ end
8
+
9
+ def self.configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ def self.configure
14
+ yield(configuration)
15
+ end
16
+
17
+ class Configuration
18
+ attr_accessor :definitions_path, :dumps_path
19
+
20
+ def initialize
21
+ @definitions_path = "spec/siringa"
22
+ @dumps_path = 'tmp/dumps'
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,38 @@
1
+ module Siringa
2
+
3
+ # Load definitions from files
4
+ def self.load_definitions
5
+ Dir[Rails.root.join("#{self.configuration.definitions_path}/**/*.rb")].each { |f| require f }
6
+ end
7
+
8
+ # Load a definition and run its code
9
+ #
10
+ # @param [Symbol] name of the definition
11
+ def self.load_definition(name)
12
+ if exists_definition?(name)
13
+ @definitions[name].call
14
+ else
15
+ raise ArgumentError, "Definition #{name.to_s} does not exist.", caller
16
+ end
17
+ end
18
+
19
+ # Add a definition
20
+ #
21
+ # @param [Symbol] name of the definition
22
+ # @param [Proc] code to run
23
+ def self.add_definition(name, &block)
24
+ if !exists_definition?(name)
25
+ @definitions[name] = Proc.new(&block)
26
+ else
27
+ raise ArgumentError, "Definition #{name.to_s} already exists."
28
+ end
29
+ end
30
+
31
+ # Check if a definition already exists
32
+ #
33
+ # @param [Symbol] name of the definition
34
+ # @return [Boolean]
35
+ def self.exists_definition?(name)
36
+ !@definitions[name].nil?
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Siringa
2
+
3
+ # Generate dump file name
4
+ #
5
+ # @return [String]
6
+ def self.dump_file_name
7
+ "#{Siringa.configuration.dumps_path}/db_#{Time.now.strftime('%Y%m%d%H%M%S')}.dump"
8
+ end
9
+
10
+ # Create a DB dump
11
+ #
12
+ # @param [String] dump path
13
+ # @return [Object]
14
+ def self.dump_to(dump_path)
15
+ adapter_config = ActiveRecord::Base.connection.instance_values["config"]
16
+ case adapter_config[:adapter]
17
+ when "mysql", "mysql2"
18
+ output = %x(/usr/bin/env mysqldump -uroot #{adapter_config[:database]} > #{dump_path})
19
+ when "sqlite3"
20
+ output = %x(/usr/bin/env sqlite3 #{adapter_config[:database]} '.backup #{dump_path}')
21
+ else
22
+ raise NotImplementedError, "Unknown adapter type '#{adapter_config[:adapter]}'"
23
+ end
24
+
25
+ { :success => $?.success?, :output => output, :dump_path => dump_path }
26
+ end
27
+
28
+ # Restore from a DB dump
29
+ #
30
+ # @param [String] dump path
31
+ # @return [Object]
32
+ def self.restore_from(dump_path)
33
+ adapter_config = ActiveRecord::Base.connection.instance_values["config"]
34
+ case adapter_config[:adapter]
35
+ when "mysql", "mysql2"
36
+ output = %x(/usr/bin/env mysql -uroot #{adapter_config[:database]} < #{dump_path})
37
+ when "sqlite3"
38
+ output = %x(/usr/bin/env sqlite3 #{adapter_config[:database]} '.restore #{dump_path}')
39
+ else
40
+ raise NotImplementedError, "Unknown adapter type '#{adapter_config[:adapter]}'"
41
+ end
42
+
43
+ { :success => $?.success?, :output => output, :dump_path => dump_path }
44
+ end
45
+
46
+ # Delete oldest dump files, keep 5 dump files
47
+ #
48
+ # @param [Array] file names
49
+ # @return [Boolean]
50
+ def self.keep_five_dumps(dump_files)
51
+ if dump_files.length > 5
52
+ dump_files.first(dump_files.length - 5).each { |f| File.delete(f) }
53
+ return true
54
+ else
55
+ return false
56
+ end
57
+ end
58
+
59
+ # Retrieve a collection of dump files sorted by creation date
60
+ #
61
+ # @return [Array]
62
+ def self.ordered_dumps
63
+ Dir.glob("#{Siringa.configuration.dumps_path}/db_*.dump").sort_by { |f| File.mtime(f) }
64
+ end
65
+
66
+ end