drobot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +678 -0
- data/README.md +12 -0
- data/Rakefile +5 -0
- data/drobot.gemspec +31 -0
- data/lib/credentials/passwordstore_provider.rb +35 -0
- data/lib/drobot.rb +40 -0
- data/lib/drobots.rb +2 -0
- data/lib/runner.rb +34 -0
- data/lib/runner_config_schema.yaml +16 -0
- data/lib/version.rb +3 -0
- data/spec/drobot_spec.rb +72 -0
- data/spec/fixtures/.gitignore +1 -0
- data/spec/fixtures/broken_config.yaml +6 -0
- data/spec/fixtures/config.ru +3 -0
- data/spec/fixtures/drobots/firstsample.rb +12 -0
- data/spec/fixtures/drobots/secondsample.rb +12 -0
- data/spec/fixtures/missing_config.yaml +8 -0
- data/spec/fixtures/mock_pass +3 -0
- data/spec/fixtures/public/sample.pdf +0 -0
- data/spec/fixtures/sample_robot.rb +15 -0
- data/spec/fixtures/simple_app.rb +22 -0
- data/spec/fixtures/test_config.yaml +8 -0
- data/spec/fixtures/views/simple_index.erb +5 -0
- data/spec/fixtures/views/simple_login.erb +11 -0
- data/spec/fixtures/views/simple_other.erb +1 -0
- data/spec/spec_helper.rb +101 -0
- metadata +234 -0
data/README.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Drobots
|
2
|
+
[![Build Status](https://travis-ci.org/yakshed/drobots.svg?branch=master)](https://travis-ci.org/yakshed/drobots)
|
3
|
+
|
4
|
+
Helpful little Drobots.
|
5
|
+
|
6
|
+
## Contributing
|
7
|
+
|
8
|
+
1. Fork it ( https://github.com/yakshed/drobots/fork )
|
9
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
10
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
11
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
12
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/drobot.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require './lib/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "drobot"
|
8
|
+
spec.version = Drobot::VERSION
|
9
|
+
spec.authors = ["Sebastian Schulze", "Lucas Dohmen"]
|
10
|
+
spec.email = ["rubygems.org@bascht.com", "lucas@moonbeamlabs.com"]
|
11
|
+
spec.summary = "Clever little Robot that downloads your Documents"
|
12
|
+
spec.description = "Clever little Robot that downloads your Documents and can't be bothered to write a description."
|
13
|
+
spec.homepage = "http://www.yakshed.org"
|
14
|
+
spec.license = "GPL-3.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "poltergeist", "~> 1.9"
|
22
|
+
spec.add_dependency "json-schema", "~> 2.5.0"
|
23
|
+
spec.add_dependency "erubis", "~> 2.7.0"
|
24
|
+
spec.add_dependency "json", "~> 1.8.3"
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
spec.add_development_dependency "awesome_print", "~> 1.6"
|
29
|
+
spec.add_development_dependency "sinatra", "~> 1.4"
|
30
|
+
spec.add_development_dependency "xdg", "~> 2.1"
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
module Credentials
|
4
|
+
class PasswordstoreProvider
|
5
|
+
|
6
|
+
def initialize(pass_command: nil, pass_name:)
|
7
|
+
default_command = '/usr/bin/pass'
|
8
|
+
@pass_command = pass_command || default_command
|
9
|
+
@pass_name = pass_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def username
|
13
|
+
credentials[:username]
|
14
|
+
end
|
15
|
+
|
16
|
+
def password
|
17
|
+
credentials[:password]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
##
|
22
|
+
# Parse pass output formed like
|
23
|
+
#
|
24
|
+
# soopa_secret
|
25
|
+
# Username: my_user
|
26
|
+
def credentials
|
27
|
+
return @credentials if @credentials
|
28
|
+
output = %x"#{@pass_command} #{@pass_name}".lines
|
29
|
+
|
30
|
+
password = output.shift
|
31
|
+
username = output.find { |line| line.start_with? 'Username:' }.split(":").pop.strip
|
32
|
+
@credentials = {:username => username, :password => password}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/drobot.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'capybara'
|
3
|
+
require 'version'
|
4
|
+
require 'drobots'
|
5
|
+
|
6
|
+
class Drobot
|
7
|
+
include Capybara::DSL
|
8
|
+
|
9
|
+
# Optimized for Programmer Happyness
|
10
|
+
BASEDIR = Pathname.new(__dir__).parent
|
11
|
+
|
12
|
+
def initialize(credential_provider, target = "/tmp/foo")
|
13
|
+
@credential_provider = credential_provider
|
14
|
+
@target = target
|
15
|
+
end
|
16
|
+
|
17
|
+
def download(url)
|
18
|
+
open("#{@target}/#{title}.pdf", 'wb') do |file|
|
19
|
+
# puts "Downloading #{url}"
|
20
|
+
file << open(url).read
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def title
|
25
|
+
drobot_name = self.class.name.split("::").pop
|
26
|
+
prefix + drobot_name.downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def prefix
|
30
|
+
Date.today.strftime("%Y-%m-")
|
31
|
+
end
|
32
|
+
|
33
|
+
def username
|
34
|
+
@credential_provider.username
|
35
|
+
end
|
36
|
+
|
37
|
+
def password
|
38
|
+
@credential_provider.password
|
39
|
+
end
|
40
|
+
end
|
data/lib/drobots.rb
ADDED
data/lib/runner.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json-schema'
|
3
|
+
require 'credentials/passwordstore_provider'
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
SCHEMA = YAML.load_file(Drobot::BASEDIR.join('lib/runner_config_schema.yaml'))
|
7
|
+
|
8
|
+
def initialize(config_file: nil)
|
9
|
+
default_file = File.join(Dir.home, '.drobots.yaml')
|
10
|
+
@config_file = config_file || default_file
|
11
|
+
@config = YAML.load_file(@config_file)
|
12
|
+
|
13
|
+
JSON::Validator.validate!(SCHEMA, @config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def drobots
|
17
|
+
@drobots ||= @config['drobots'].map do |name, config|
|
18
|
+
credential_provider = Credentials::PasswordstoreProvider.new(pass_name: config['passwordstore']['name'])
|
19
|
+
determine_drobot(name).new(credential_provider)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
drobots.each(&:run)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def determine_drobot(name)
|
30
|
+
drobot = Object.const_get("Drobots::#{name}")
|
31
|
+
rescue NameError
|
32
|
+
raise "unknown Drobot #{name}"
|
33
|
+
end
|
34
|
+
end
|
data/lib/version.rb
ADDED
data/spec/drobot_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pp'
|
3
|
+
require 'drobot'
|
4
|
+
require 'runner'
|
5
|
+
require 'credentials/passwordstore_provider'
|
6
|
+
|
7
|
+
require_relative 'fixtures/simple_app'
|
8
|
+
require_relative 'fixtures/sample_robot'
|
9
|
+
require_relative 'fixtures/drobots/firstsample'
|
10
|
+
require_relative 'fixtures/drobots/secondsample'
|
11
|
+
|
12
|
+
describe "Drobot", :type => :app do
|
13
|
+
let(:fixture_dir) { Drobot::BASEDIR.join("spec/fixtures") }
|
14
|
+
let(:output_dir) { Drobot::BASEDIR.join("tmp") }
|
15
|
+
|
16
|
+
describe "runner" do
|
17
|
+
context "with valid configuration" do
|
18
|
+
subject { Runner.new(config_file: File.join(fixture_dir, 'test_config.yaml')) }
|
19
|
+
|
20
|
+
it "iterates over multiple drobots" do
|
21
|
+
drobots = subject.drobots
|
22
|
+
expect(drobots.count).to eq 2
|
23
|
+
expect(drobots.first).to be_a(Drobots::Firstsample)
|
24
|
+
expect(drobots.last).to be_a(Drobots::Secondsample)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "runs Drobots" do
|
28
|
+
subject.run
|
29
|
+
|
30
|
+
expect(subject.drobots.first.ran?).to be true
|
31
|
+
expect(subject.drobots.last.ran?).to be true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "with broken configurations" do
|
35
|
+
it "gives a proper warning" do
|
36
|
+
expect {
|
37
|
+
Runner.new(config_file: File.join(fixture_dir, 'broken_config.yaml'))
|
38
|
+
}.to raise_exception(JSON::Schema::ValidationError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "detects non-existing Drobots" do
|
42
|
+
expect {
|
43
|
+
Runner.new(config_file: File.join(fixture_dir, 'missing_config.yaml')).run
|
44
|
+
}.to raise_exception(RuntimeError, /unknown Drobot NonExistingDrobot/)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "automated download", :type => :feature do
|
50
|
+
|
51
|
+
let(:runner) { Runner.new }
|
52
|
+
let(:credential_provider) { Credentials::PasswordstoreProvider.new(pass_command: "#{fixture_dir}/mock_pass", pass_name: 'sample/app') }
|
53
|
+
subject { Drobots::Sample.new(credential_provider, output_dir) }
|
54
|
+
|
55
|
+
before do
|
56
|
+
Capybara.app = SimpleApp.new
|
57
|
+
FileUtils.mkdir_p(output_dir)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sorts files into the correct folder." do
|
61
|
+
files = proc { Dir["#{output_dir}/**"] }
|
62
|
+
FileUtils.rm(files.call)
|
63
|
+
|
64
|
+
expect(files.call).to eq []
|
65
|
+
|
66
|
+
subject.run
|
67
|
+
|
68
|
+
filename = Date.today.strftime("%Y-%m-sample.pdf")
|
69
|
+
expect(files.call).to eq [ File.join(output_dir, filename) ]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
output/*
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Drobots::Sample < Drobot
|
2
|
+
def run
|
3
|
+
visit "/"
|
4
|
+
click_link 'login'
|
5
|
+
|
6
|
+
within('#myform') do
|
7
|
+
fill_in 'username', :with => username
|
8
|
+
fill_in 'password', :with => password
|
9
|
+
click_button 'Yo'
|
10
|
+
end
|
11
|
+
|
12
|
+
l = find_link 'Mega Secure Download'
|
13
|
+
download(l[:href])
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'tilt/erubis'
|
3
|
+
|
4
|
+
class SimpleApp < Sinatra::Base
|
5
|
+
get '/' do
|
6
|
+
erb :simple_index
|
7
|
+
end
|
8
|
+
|
9
|
+
get '/login' do
|
10
|
+
erb :simple_login
|
11
|
+
end
|
12
|
+
|
13
|
+
post '/login' do
|
14
|
+
@authenticated = (@params[:username] == 'my_user' && @params[:password] == 'soopa_secret')
|
15
|
+
erb :simple_login
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/other' do
|
19
|
+
erb :simple_other
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% if @authenticated -%>
|
2
|
+
<a href="/sample.pdf">Mega Secure Download</a>
|
3
|
+
<% else %>
|
4
|
+
<h1>Go away</h1>
|
5
|
+
<% end %>
|
6
|
+
<hr/>
|
7
|
+
<form id="myform" action="/login" method="post">
|
8
|
+
<input name="username" type="text" value=""/>
|
9
|
+
<input name="password" type="text" value=""/>
|
10
|
+
<input name="submit" type="submit" value="Yo" />
|
11
|
+
</form>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hurrah!</h1>
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'capybara/rspec'
|
2
|
+
require 'capybara/poltergeist'
|
3
|
+
|
4
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
7
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
8
|
+
# files.
|
9
|
+
#
|
10
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
11
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
12
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
13
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
14
|
+
# a separate helper file that requires the additional dependencies and performs
|
15
|
+
# the additional setup, and require it from the spec files that actually need
|
16
|
+
# it.
|
17
|
+
#
|
18
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
19
|
+
# users commonly want.
|
20
|
+
#
|
21
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# rspec-expectations config goes here. You can use an alternate
|
24
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
25
|
+
# assertions if you prefer.
|
26
|
+
config.expect_with :rspec do |expectations|
|
27
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
28
|
+
# and `failure_message` of custom matchers include text for helper methods
|
29
|
+
# defined using `chain`, e.g.:
|
30
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
31
|
+
# # => "be bigger than 2 and smaller than 4"
|
32
|
+
# ...rather than:
|
33
|
+
# # => "be bigger than 2"
|
34
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
35
|
+
end
|
36
|
+
|
37
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
38
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
39
|
+
config.mock_with :rspec do |mocks|
|
40
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
41
|
+
# a real object. This is generally recommended, and will default to
|
42
|
+
# `true` in RSpec 4.
|
43
|
+
mocks.verify_partial_doubles = true
|
44
|
+
end
|
45
|
+
|
46
|
+
# The settings below are suggested to provide a good initial experience
|
47
|
+
# with RSpec, but feel free to customize to your heart's content.
|
48
|
+
=begin
|
49
|
+
# These two settings work together to allow you to limit a spec run
|
50
|
+
# to individual examples or groups you care about by tagging them with
|
51
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
52
|
+
# get run.
|
53
|
+
config.filter_run :focus
|
54
|
+
config.run_all_when_everything_filtered = true
|
55
|
+
|
56
|
+
# Allows RSpec to persist some state between runs in order to support
|
57
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
58
|
+
# you configure your source control system to ignore this file.
|
59
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
60
|
+
|
61
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
62
|
+
# recommended. For more details, see:
|
63
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
64
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
65
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
66
|
+
config.disable_monkey_patching!
|
67
|
+
|
68
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
69
|
+
# be too noisy due to issues in dependencies.
|
70
|
+
config.warnings = true
|
71
|
+
|
72
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
73
|
+
# file, and it's useful to allow more verbose output when running an
|
74
|
+
# individual spec file.
|
75
|
+
if config.files_to_run.one?
|
76
|
+
# Use the documentation formatter for detailed output,
|
77
|
+
# unless a formatter has already been configured
|
78
|
+
# (e.g. via a command-line flag).
|
79
|
+
config.default_formatter = 'doc'
|
80
|
+
end
|
81
|
+
|
82
|
+
# Print the 10 slowest examples and example groups at the
|
83
|
+
# end of the spec run, to help surface which specs are running
|
84
|
+
# particularly slow.
|
85
|
+
config.profile_examples = 10
|
86
|
+
|
87
|
+
# Run specs in random order to surface order dependencies. If you find an
|
88
|
+
# order dependency and want to debug it, you can fix the order by providing
|
89
|
+
# the seed, which is printed after each run.
|
90
|
+
# --seed 1234
|
91
|
+
config.order = :random
|
92
|
+
|
93
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
94
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
95
|
+
# test failures related to randomization by passing the same `--seed` value
|
96
|
+
# as the one that triggered the failure.
|
97
|
+
Kernel.srand config.seed
|
98
|
+
=end
|
99
|
+
end
|
100
|
+
|
101
|
+
Capybara.default_driver = :poltergeist
|