et 0.5.9 → 0.6.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +20 -14
- data/README.md +17 -0
- data/et.gemspec +3 -1
- data/lib/et.rb +1 -0
- data/lib/et/api.rb +40 -60
- data/lib/et/fallback_connection.rb +29 -0
- data/lib/et/lesson.rb +10 -14
- data/lib/et/runner.rb +2 -2
- data/lib/et/submission_file_list.rb +50 -0
- data/lib/et/version.rb +1 -1
- data/spec/cli/get_lesson_spec.rb +14 -14
- data/spec/cli/list_lessons_spec.rb +1 -1
- data/spec/data/bloated-challenge/.lesson.yml +7 -0
- data/spec/data/bloated-challenge/bloated-challenge.md +3 -0
- data/spec/data/bloated-challenge/node_modules/boo/somefile.js +1 -0
- data/spec/data/bloated-challenge/problem.rb +1 -0
- data/spec/data/bloated-challenge/rando_folder/rando_file.js +1 -0
- data/spec/lib/api_spec.rb +55 -89
- data/spec/lib/fallback_connection_spec.rb +42 -0
- data/spec/lib/submission_file_list_spec.rb +27 -0
- data/spec/spec_helper.rb +0 -5
- metadata +50 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e414688a6eb0fc5a464071284eaad87cf914f16
|
4
|
+
data.tar.gz: 647e0fbd0c9fadbe733f480e4d29db3d06e6a18a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8b87ae613c19d680bc2219b596e980351f04ddf9baee09efc1ce1624f9885e150db28312b8430c983ceccb90e78a701128afb8868607274dd6bcbdcf5515ec0
|
7
|
+
data.tar.gz: 7cdb0f85e3fecc7aa82431e8d3e1cda5d745b65582ccfbefe8539868ab29e19e5ea161796e7d54c12d2ab6c7d019185d0f380e78e31208add0ec1a747e5d74b2
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
et (0.
|
4
|
+
et (0.6.0)
|
5
|
+
faraday (~> 0.9)
|
6
|
+
faraday_middleware (~> 0.10)
|
5
7
|
gli (= 2.11.0)
|
6
8
|
multipart-post (~> 2.0)
|
9
|
+
rake (~> 10)
|
7
10
|
rspec (~> 3.0)
|
8
11
|
rspec-mocks (~> 3.0)
|
9
12
|
|
@@ -12,6 +15,10 @@ GEM
|
|
12
15
|
specs:
|
13
16
|
coderay (1.1.1)
|
14
17
|
diff-lcs (1.2.5)
|
18
|
+
faraday (0.9.2)
|
19
|
+
multipart-post (>= 1.2, < 3)
|
20
|
+
faraday_middleware (0.10.0)
|
21
|
+
faraday (>= 0.7.4, < 0.10)
|
15
22
|
gli (2.11.0)
|
16
23
|
method_source (0.8.2)
|
17
24
|
multipart-post (2.0.0)
|
@@ -20,19 +27,19 @@ GEM
|
|
20
27
|
method_source (~> 0.8.1)
|
21
28
|
slop (~> 3.4)
|
22
29
|
rake (10.5.0)
|
23
|
-
rspec (3.
|
24
|
-
rspec-core (~> 3.
|
25
|
-
rspec-expectations (~> 3.
|
26
|
-
rspec-mocks (~> 3.
|
27
|
-
rspec-core (3.
|
28
|
-
rspec-support (~> 3.
|
29
|
-
rspec-expectations (3.
|
30
|
+
rspec (3.5.0)
|
31
|
+
rspec-core (~> 3.5.0)
|
32
|
+
rspec-expectations (~> 3.5.0)
|
33
|
+
rspec-mocks (~> 3.5.0)
|
34
|
+
rspec-core (3.5.4)
|
35
|
+
rspec-support (~> 3.5.0)
|
36
|
+
rspec-expectations (3.5.0)
|
30
37
|
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
-
rspec-support (~> 3.
|
32
|
-
rspec-mocks (3.
|
38
|
+
rspec-support (~> 3.5.0)
|
39
|
+
rspec-mocks (3.5.0)
|
33
40
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
-
rspec-support (~> 3.
|
35
|
-
rspec-support (3.
|
41
|
+
rspec-support (~> 3.5.0)
|
42
|
+
rspec-support (3.5.0)
|
36
43
|
slop (3.6.0)
|
37
44
|
|
38
45
|
PLATFORMS
|
@@ -41,7 +48,6 @@ PLATFORMS
|
|
41
48
|
DEPENDENCIES
|
42
49
|
et!
|
43
50
|
pry (~> 0)
|
44
|
-
rake (~> 10)
|
45
51
|
|
46
52
|
BUNDLED WITH
|
47
|
-
1.
|
53
|
+
1.13.5
|
data/README.md
CHANGED
@@ -28,3 +28,20 @@ COMMANDS
|
|
28
28
|
submit - Submit the lesson in this directory.
|
29
29
|
test - Run an exercise test suite.
|
30
30
|
```
|
31
|
+
|
32
|
+
### Releasing New Versions
|
33
|
+
|
34
|
+
Bundler provided `gem_tasks` have been incorporated into this libraries
|
35
|
+
`Rakefile`. Thankfully these tasks make releasing new gem versions a snap!
|
36
|
+
|
37
|
+
To release a new version of this gem:
|
38
|
+
|
39
|
+
1. Bump the version according to [semantic versioning](http://semver.org/)
|
40
|
+
2. Perform a git commit
|
41
|
+
3. Run `rake release` from the project root
|
42
|
+
|
43
|
+
Bundler's provided rake task will appropriately push a tag to GitHub and the gem
|
44
|
+
itself to [rubygems.org](https://rubygems.org)
|
45
|
+
|
46
|
+
_Note:_ in order to release the gem you must be an authorized owner of it on
|
47
|
+
[rubygems.org](https://rubygems.org)
|
data/et.gemspec
CHANGED
@@ -19,10 +19,12 @@ DESC
|
|
19
19
|
s.bindir = "bin"
|
20
20
|
s.executables << "et"
|
21
21
|
s.license = "MIT"
|
22
|
-
s.add_development_dependency("rake", "~> 10")
|
23
22
|
s.add_development_dependency("pry", '~> 0')
|
24
23
|
s.add_runtime_dependency("rspec", "~> 3.0")
|
24
|
+
s.add_runtime_dependency("rake", "~> 10")
|
25
25
|
s.add_runtime_dependency("rspec-mocks", "~> 3.0")
|
26
26
|
s.add_runtime_dependency("multipart-post", "~> 2.0")
|
27
27
|
s.add_runtime_dependency("gli", "2.11.0")
|
28
|
+
s.add_runtime_dependency("faraday", "~> 0.9")
|
29
|
+
s.add_runtime_dependency("faraday_middleware", "~> 0.10")
|
28
30
|
end
|
data/lib/et.rb
CHANGED
@@ -3,6 +3,7 @@ require_relative "et/version"
|
|
3
3
|
require_relative "et/runner"
|
4
4
|
require_relative "et/operating_system"
|
5
5
|
require_relative "et/api"
|
6
|
+
require_relative "et/submission_file_list"
|
6
7
|
require_relative "et/config"
|
7
8
|
require_relative "et/formatter"
|
8
9
|
require_relative "et/lesson"
|
data/lib/et/api.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require "net/http/post/multipart"
|
2
1
|
require "securerandom"
|
3
2
|
require "base64"
|
4
3
|
require "json"
|
5
4
|
require "openssl"
|
6
5
|
|
6
|
+
require_relative "fallback_connection"
|
7
7
|
module ET
|
8
8
|
class API
|
9
9
|
attr_reader :host, :username, :token
|
@@ -15,30 +15,29 @@ module ET
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def list_lessons
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
resp = nil
|
19
|
+
api_client.with_ssl_fallback do |client|
|
20
|
+
resp = client.get('/lessons.json', :submittable => 1)
|
21
|
+
end
|
22
|
+
resp.body['lessons']
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_lesson(slug)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
body = JSON.parse(response.body, symbolize_names: true)
|
32
|
-
body[:lesson]
|
26
|
+
resp = nil
|
27
|
+
api_client.with_ssl_fallback do |client|
|
28
|
+
resp = client.get(lesson_url(slug), :submittable => 1)
|
29
|
+
end
|
30
|
+
resp.body['lesson']
|
33
31
|
end
|
34
32
|
|
35
33
|
def download_file(url)
|
36
|
-
|
34
|
+
response = nil
|
37
35
|
dest = random_filename
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
download_client(url).with_ssl_fallback do |client|
|
38
|
+
response = client.get(URI(url).path)
|
39
|
+
end
|
40
|
+
if response.status == 200
|
42
41
|
open(dest, 'wb') do |file|
|
43
42
|
file.write(response.body)
|
44
43
|
end
|
@@ -50,66 +49,47 @@ module ET
|
|
50
49
|
|
51
50
|
def submit_lesson(lesson)
|
52
51
|
submission_file = lesson.archive!
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
"submission
|
58
|
-
request["Authorization"] = auth_header
|
59
|
-
|
60
|
-
issue_request(request)
|
52
|
+
io = Faraday::UploadIO.new(submission_file, "application/x-tar")
|
53
|
+
resp = nil
|
54
|
+
api_client.with_ssl_fallback do |client|
|
55
|
+
resp = client.post(submission_url(lesson.slug),
|
56
|
+
"submission" => { "archive" => io})
|
61
57
|
end
|
58
|
+
resp
|
62
59
|
end
|
63
60
|
|
64
61
|
private
|
65
|
-
def issue_request(request, url = nil)
|
66
|
-
uri = URI.parse(url || @host)
|
67
|
-
begin
|
68
|
-
Net::HTTP.start(uri.host, uri.port,
|
69
|
-
use_ssl: uri.scheme == "https") do |http|
|
70
|
-
|
71
|
-
http.request(request)
|
72
|
-
end
|
73
|
-
rescue OpenSSL::SSL::SSLError => e
|
74
|
-
if operating_system.platform_family?(:windows)
|
75
|
-
https = Net::HTTP.new(uri.host, uri.port)
|
76
|
-
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
77
|
-
https.use_ssl = uri.scheme == 'https'
|
78
|
-
https.start do |http|
|
79
|
-
http.request(request)
|
80
|
-
end
|
81
|
-
else
|
82
|
-
raise e
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
62
|
def lesson_url(slug)
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def lessons_url
|
92
|
-
URI.join(host, "lessons.json?submittable=1")
|
63
|
+
"/lessons/#{slug}.json"
|
93
64
|
end
|
94
65
|
|
95
66
|
def submission_url(slug)
|
96
|
-
|
67
|
+
"/lessons/#{slug}/submissions.json"
|
97
68
|
end
|
98
69
|
|
99
70
|
def random_filename
|
100
71
|
File.join(Dir.mktmpdir, SecureRandom.hex)
|
101
72
|
end
|
102
73
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
74
|
+
def api_client
|
75
|
+
@api_client ||= ET::FallbackConnection.new(:url => @host) do |client|
|
76
|
+
client.request :multipart
|
106
77
|
|
107
|
-
|
108
|
-
|
78
|
+
client.request :url_encoded
|
79
|
+
client.request :basic_auth, username, token
|
80
|
+
|
81
|
+
client.response :json, :content_type => /\bjson$/
|
82
|
+
|
83
|
+
client.adapter Faraday.default_adapter
|
84
|
+
end
|
109
85
|
end
|
110
86
|
|
111
|
-
def
|
112
|
-
|
87
|
+
def download_client(url)
|
88
|
+
uri = URI(url)
|
89
|
+
scheme_and_host = [uri.scheme, uri.host].join('://')
|
90
|
+
ET::FallbackConnection.new(:url => scheme_and_host) do |client|
|
91
|
+
client.adapter Faraday.default_adapter
|
92
|
+
end
|
113
93
|
end
|
114
94
|
end
|
115
95
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware"
|
3
|
+
|
4
|
+
module ET
|
5
|
+
class FallbackConnection
|
6
|
+
def initialize(opts = {}, &block)
|
7
|
+
@connection = Faraday.new(opts, &block)
|
8
|
+
|
9
|
+
@fallback_connection = Faraday.new(opts.merge(:ssl => {:verify => false}))
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_ssl_fallback(&block)
|
13
|
+
begin
|
14
|
+
block.call(@connection)
|
15
|
+
rescue OpenSSL::SSL::SSLError => e
|
16
|
+
if operating_system.platform_family?(:windows)
|
17
|
+
block.call(@fallback_connection)
|
18
|
+
else
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def operating_system
|
26
|
+
@os ||= ET::OperatingSystem.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/et/lesson.rb
CHANGED
@@ -17,16 +17,16 @@ module ET
|
|
17
17
|
File.open(filepath, "wb") do |file|
|
18
18
|
Zlib::GzipWriter.wrap(file) do |gz|
|
19
19
|
Gem::Package::TarWriter.new(gz) do |tar|
|
20
|
-
|
21
|
-
relative_path = file
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
ET::SubmissionFileList.new(dir).each do |file|
|
21
|
+
relative_path = file
|
22
|
+
absolute_path = File.join(dir, file)
|
23
|
+
|
24
|
+
if FileTest.directory?(absolute_path)
|
25
|
+
tar.mkdir(relative_path, 0755)
|
26
|
+
else
|
27
|
+
file_contents = File.read(absolute_path)
|
28
|
+
tar.add_file_simple("./" + relative_path, 0555, file_contents.bytesize) do |io|
|
29
|
+
io.write(file_contents)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -51,10 +51,6 @@ module ET
|
|
51
51
|
!dir.nil?
|
52
52
|
end
|
53
53
|
|
54
|
-
def ignored_files
|
55
|
-
(config["ignore"] || []) + [".lesson.yml"]
|
56
|
-
end
|
57
|
-
|
58
54
|
protected
|
59
55
|
|
60
56
|
def config
|
data/lib/et/runner.rb
CHANGED
@@ -40,7 +40,7 @@ module ET
|
|
40
40
|
desc "List available lessons."
|
41
41
|
command :list do |c|
|
42
42
|
c.action do |_global_options, _options, _cmdargs|
|
43
|
-
Formatter.print_table(api.list_lessons,
|
43
|
+
Formatter.print_table(api.list_lessons, 'slug', 'title', 'type')
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -49,7 +49,7 @@ module ET
|
|
49
49
|
c.action do |_global_options, _options, cmdargs|
|
50
50
|
cmdargs.each do |slug|
|
51
51
|
lesson = api.get_lesson(slug)
|
52
|
-
archive = api.download_file(lesson[
|
52
|
+
archive = api.download_file(lesson['archive_url'])
|
53
53
|
archive_manager = ET::ArchiveManager.new(archive, cwd)
|
54
54
|
archive_manager.unpack
|
55
55
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rake/file_list"
|
2
|
+
|
3
|
+
module ET
|
4
|
+
class SubmissionFileList
|
5
|
+
include Enumerable
|
6
|
+
DEFAULT_IGNORE_GLOBS = [
|
7
|
+
'.lesson.yml',
|
8
|
+
'node_modules/**/*'
|
9
|
+
]
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def files
|
16
|
+
unless @files
|
17
|
+
@files = Rake::FileList[File.join(@path, "**/*")]
|
18
|
+
ignore_globs.each do |glob|
|
19
|
+
@files = @files.exclude(File.join(@path, glob))
|
20
|
+
end
|
21
|
+
@files = @files.sub(File.join(@path, "/"), "")
|
22
|
+
end
|
23
|
+
|
24
|
+
@files
|
25
|
+
end
|
26
|
+
|
27
|
+
def each(&block)
|
28
|
+
files.each do |file|
|
29
|
+
block.call(file)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def ignore_globs
|
35
|
+
lesson_ignore_globs + [] + DEFAULT_IGNORE_GLOBS
|
36
|
+
end
|
37
|
+
|
38
|
+
def lesson_ignore_globs
|
39
|
+
unless @lesson_ignore_globs
|
40
|
+
lesson_yml = File.join(@path, '.lesson.yml')
|
41
|
+
if FileTest.exists?(lesson_yml)
|
42
|
+
@lesson_ignore_globs = YAML.load_file(lesson_yml)['ignore'] || []
|
43
|
+
else
|
44
|
+
@lesson_ignore_globs = []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@lesson_ignore_globs
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/et/version.rb
CHANGED
data/spec/cli/get_lesson_spec.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
-
describe
|
1
|
+
describe 'get lesson' do
|
2
2
|
let(:lesson_info) do
|
3
3
|
{
|
4
|
-
title
|
5
|
-
slug
|
6
|
-
archive_url
|
4
|
+
'title' => 'Some Challenge',
|
5
|
+
'slug' => 'some-challenge',
|
6
|
+
'archive_url' => "http://localhost:3000/some-challenge.tar.gz"
|
7
7
|
}
|
8
8
|
end
|
9
9
|
|
10
10
|
let(:sample_archive_path) do
|
11
|
-
project_root.join(
|
11
|
+
project_root.join('spec/data/some-challenge.tar.gz')
|
12
12
|
end
|
13
13
|
|
14
|
-
context
|
15
|
-
it
|
16
|
-
tmp_archive_path = File.join(Dir.tmpdir,
|
17
|
-
system(
|
14
|
+
context 'when in a working area' do
|
15
|
+
it 'downloads and extracts the lesson' do
|
16
|
+
tmp_archive_path = File.join(Dir.tmpdir, 'some-challenge.tar.gz')
|
17
|
+
system('cp', sample_archive_path.to_s, tmp_archive_path)
|
18
18
|
|
19
19
|
expect_any_instance_of(ET::API).to receive(:get_lesson).
|
20
|
-
with(
|
20
|
+
with('some-challenge').
|
21
21
|
and_return(lesson_info)
|
22
22
|
|
23
23
|
expect_any_instance_of(ET::API).to receive(:download_file).
|
24
|
-
with(
|
24
|
+
with('http://localhost:3000/some-challenge.tar.gz').
|
25
25
|
and_return(tmp_archive_path)
|
26
26
|
|
27
|
-
Dir.mktmpdir(
|
27
|
+
Dir.mktmpdir('test') do |tmpdir|
|
28
28
|
write_sample_config_to(tmpdir)
|
29
29
|
|
30
30
|
runner = ET::Runner.new(tmpdir)
|
31
31
|
_, _ = capture_output do
|
32
|
-
expect(runner.go([
|
32
|
+
expect(runner.go(['get', 'some-challenge'])).to eq(0)
|
33
33
|
end
|
34
34
|
|
35
|
-
[
|
35
|
+
['some-challenge/README.md', 'some-challenge/sample.rb'].each do |filename|
|
36
36
|
path = File.join(tmpdir, filename)
|
37
37
|
expect(File.exist?(path)).to eq(true)
|
38
38
|
end
|
@@ -2,7 +2,7 @@ describe "list lessons" do
|
|
2
2
|
let(:sample_lessons_file) { project_root.join("spec/data/lessons.json") }
|
3
3
|
|
4
4
|
let(:sample_lessons) do
|
5
|
-
JSON.parse(File.read(sample_lessons_file)
|
5
|
+
JSON.parse(File.read(sample_lessons_file))['lessons']
|
6
6
|
end
|
7
7
|
|
8
8
|
it "prints the titles and slug" do
|
@@ -0,0 +1 @@
|
|
1
|
+
alert('please do not include this file');
|
@@ -0,0 +1 @@
|
|
1
|
+
# YOUR CODE GOES HERE
|
@@ -0,0 +1 @@
|
|
1
|
+
console.log('I am random');
|
data/spec/lib/api_spec.rb
CHANGED
@@ -10,26 +10,24 @@ describe ET::API do
|
|
10
10
|
|
11
11
|
describe "lessons" do
|
12
12
|
it "queries for a list of lessons" do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
expect(http).to receive(:request).and_return(response)
|
25
|
-
expect(response).to receive(:body).and_return(lessons_response)
|
13
|
+
client = Faraday.new do |builder|
|
14
|
+
builder.response :json
|
15
|
+
|
16
|
+
builder.adapter :test do |stubs|
|
17
|
+
stubs.get("/lessons.json") do
|
18
|
+
[200, {}, lessons_response]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
allow_any_instance_of(ET::FallbackConnection).to receive(:with_ssl_fallback).and_yield(client)
|
26
24
|
|
27
25
|
results = api.list_lessons
|
28
26
|
|
29
27
|
expect(results.count).to eq(3)
|
30
|
-
expect(results[0][
|
31
|
-
expect(results[0][
|
32
|
-
expect(results[0][
|
28
|
+
expect(results[0]['title']).to eq("Max Number")
|
29
|
+
expect(results[0]['slug']).to eq("max-number")
|
30
|
+
expect(results[0]['type']).to eq("exercise")
|
33
31
|
end
|
34
32
|
|
35
33
|
let(:lesson_response) do
|
@@ -37,99 +35,67 @@ describe ET::API do
|
|
37
35
|
end
|
38
36
|
|
39
37
|
it "queries for a single lesson" do
|
40
|
-
|
41
|
-
|
42
|
-
http = double
|
43
|
-
|
44
|
-
lesson_uri = URI("http://localhost:3000/lessons/rock-paper-scissors.json?submittable=1")
|
45
|
-
expect(Net::HTTP::Get).to receive(:new).
|
46
|
-
with(lesson_uri).
|
47
|
-
and_return(request)
|
48
|
-
expect(Net::HTTP).to receive(:start).with(
|
49
|
-
lesson_uri.host,
|
50
|
-
lesson_uri.port,
|
51
|
-
use_ssl: lesson_uri.scheme == "https").
|
52
|
-
and_yield(http)
|
53
|
-
expect(http).to receive(:request).and_return(response)
|
54
|
-
expect(response).to receive(:body).and_return(lesson_response)
|
55
|
-
|
56
|
-
result = api.get_lesson("rock-paper-scissors")
|
38
|
+
client = Faraday.new do |builder|
|
39
|
+
builder.response :json
|
57
40
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
41
|
+
builder.adapter :test do |stubs|
|
42
|
+
stubs.get("/lessons/rock-paper-scissors.json") do
|
43
|
+
[200, {}, lesson_response]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
62
47
|
|
63
|
-
|
64
|
-
|
65
|
-
dbl_os = double
|
66
|
-
allow(dbl_os).to receive(:platform_family?).with(:windows).and_return(false)
|
67
|
-
expect(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
48
|
+
allow_any_instance_of(ET::FallbackConnection).
|
49
|
+
to receive(:with_ssl_fallback).and_yield(client)
|
68
50
|
|
69
|
-
|
70
|
-
expect{ api.list_lessons }.to raise_error(OpenSSL::SSL::SSLError)
|
71
|
-
end
|
51
|
+
result = api.get_lesson("rock-paper-scissors")
|
72
52
|
|
73
|
-
|
74
|
-
http
|
75
|
-
allow(http).to receive(:start).and_yield(http)
|
76
|
-
allow(http).to receive(:verify_mode=)
|
77
|
-
allow(http).to receive(:use_ssl=)
|
78
|
-
response = double
|
79
|
-
|
80
|
-
allow(http).to receive(:request).and_return(response)
|
81
|
-
allow(response).to receive(:body).and_return(lessons_response)
|
82
|
-
|
83
|
-
allow(Net::HTTP).to receive(:start).
|
84
|
-
with(
|
85
|
-
lessons_uri.host,
|
86
|
-
lessons_uri.port,
|
87
|
-
use_ssl: lessons_uri.scheme == 'https').
|
88
|
-
and_raise(OpenSSL::SSL::SSLError)
|
89
|
-
|
90
|
-
expect(Net::HTTP).to receive(:new).with(
|
91
|
-
lessons_uri.host,
|
92
|
-
lessons_uri.port).
|
93
|
-
and_return(http)
|
94
|
-
|
95
|
-
dbl_os = double
|
96
|
-
allow(dbl_os).to receive(:platform_family?).and_return(:windows)
|
97
|
-
allow(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
98
|
-
|
99
|
-
api.list_lessons
|
53
|
+
expect(result['title']).to eq("Rock, Paper, Scissors")
|
54
|
+
expect(result['archive_url']).to eq('http://example.com/rock-paper-scissors.tar.gz')
|
100
55
|
end
|
101
|
-
end
|
56
|
+
end
|
102
57
|
|
103
58
|
context 'downloading files' do
|
104
59
|
it 'returns nil when a 404 is encountered' do
|
60
|
+
filename = 'somefile.tar.gz'
|
61
|
+
client = Faraday.new do |builder|
|
62
|
+
builder.response :json
|
105
63
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
64
|
+
builder.adapter :test do |stubs|
|
65
|
+
stubs.get("/#{filename}") do
|
66
|
+
[404, {}, '']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
111
70
|
|
112
|
-
|
71
|
+
allow_any_instance_of(ET::FallbackConnection).
|
72
|
+
to receive(:with_ssl_fallback).and_yield(client)
|
73
|
+
|
74
|
+
expect(api.download_file("http://example.com/#{filename}")).to be_nil
|
113
75
|
end
|
76
|
+
|
114
77
|
it 'returns a local file when a challenge is successfully downloaded' do
|
115
78
|
path = '/tmp/et'
|
116
|
-
filename = '
|
79
|
+
filename = 'some-challenge.tar.gz'
|
117
80
|
|
118
81
|
FileUtils.rm_rf(File.join(path, filename))
|
119
82
|
FileUtils.mkdir_p(path)
|
120
83
|
|
84
|
+
filename = 'somefile.tar.gz'
|
85
|
+
client = Faraday.new do |builder|
|
86
|
+
builder.adapter :test do |stubs|
|
87
|
+
stubs.get("/#{filename}") do
|
88
|
+
[200, {}, '']
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
allow_any_instance_of(ET::FallbackConnection).
|
94
|
+
to receive(:with_ssl_fallback).and_yield(client)
|
121
95
|
allow(Dir).to receive(:mktmpdir).and_return(path)
|
122
96
|
allow(SecureRandom).to receive(:hex).and_return(filename)
|
123
97
|
|
124
|
-
|
125
|
-
response = double
|
126
|
-
file_contents = File.read(File.join(File.dirname(__FILE__), "../data/some-challenge.tar.gz"))
|
127
|
-
allow(response).to receive(:body).and_return(file_contents)
|
128
|
-
allow(response).to receive(:code).and_return("200")
|
129
|
-
allow(http).to receive(:request).and_return(response)
|
130
|
-
allow(Net::HTTP).to receive(:start).and_yield(http)
|
131
|
-
|
132
|
-
url = 'http://example.com/some-challenge.tar.gz'
|
98
|
+
url = "http://example.com/#{filename}"
|
133
99
|
|
134
100
|
expect(api.download_file(url)).to eql(File.join(path, filename))
|
135
101
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
describe ET::FallbackConnection do
|
2
|
+
let(:cnn) do
|
3
|
+
ET::FallbackConnection.new(:url => 'http://localhost')
|
4
|
+
end
|
5
|
+
|
6
|
+
context 'ssl verification' do
|
7
|
+
it 're-raises an exception for non Windows machines' do
|
8
|
+
dbl_os = double
|
9
|
+
allow(dbl_os).to receive(:platform_family?).with(:windows).and_return(false)
|
10
|
+
expect(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
11
|
+
|
12
|
+
allow_any_instance_of(Faraday::Connection).
|
13
|
+
to receive(:get).and_raise(OpenSSL::SSL::SSLError)
|
14
|
+
|
15
|
+
expect{ cnn.with_ssl_fallback{|client| client.get('/') }}.
|
16
|
+
to raise_error(OpenSSL::SSL::SSLError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'swallows the exception for windows machines and reissues' do
|
20
|
+
dbl_os = double
|
21
|
+
allow(dbl_os).to receive(:platform_family?).and_return(:windows)
|
22
|
+
allow(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
23
|
+
|
24
|
+
called_twice = false
|
25
|
+
allow_any_instance_of(Faraday::Connection).to receive(:get) do |*args|
|
26
|
+
if args[0].ssl.verify != false
|
27
|
+
#simulate a windows SSL verification failed
|
28
|
+
raise OpenSSL::SSL::SSLError
|
29
|
+
elsif args[0].ssl.verify == false
|
30
|
+
#flip the switch that the request was reissued without SSL verification
|
31
|
+
called_twice = true
|
32
|
+
double(:body => '{}')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
cnn.with_ssl_fallback do |client|
|
37
|
+
client.get('/')
|
38
|
+
end
|
39
|
+
expect(called_twice).to be(true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
describe ET::SubmissionFileList do
|
2
|
+
let(:file_list) do
|
3
|
+
path = File.expand_path(File.join(
|
4
|
+
File.dirname(__FILE__), "../data/bloated-challenge"))
|
5
|
+
ET::SubmissionFileList.new(path)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'includes a relevant file' do
|
9
|
+
expect(file_list).to include('problem.rb')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'ignores a standard file' do
|
13
|
+
expect(file_list).to_not include('sample-challenge.md')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'ignores a file included in the default globs' do
|
17
|
+
expect(file_list).to_not include('node_modules/somefile.js')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'ignores a file included in a glob' do
|
21
|
+
expect(file_list).to_not include('node_modules/boo/somefile.js')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'ignores a file included in the lesson.yml ignore' do
|
25
|
+
expect(file_list).to_not include('rando_folder/rando_file.js')
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: et
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Sheehan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: pry
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '3.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '10'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec-mocks
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - '='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 2.11.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: faraday
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.9'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.9'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: faraday_middleware
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.10'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.10'
|
97
125
|
description: |
|
98
126
|
Users can download challenges and submit their solutions via the command-line
|
99
127
|
interface.
|
@@ -120,16 +148,23 @@ files:
|
|
120
148
|
- lib/et/challenge.rb
|
121
149
|
- lib/et/config.rb
|
122
150
|
- lib/et/exercise.rb
|
151
|
+
- lib/et/fallback_connection.rb
|
123
152
|
- lib/et/formatter.rb
|
124
153
|
- lib/et/lesson.rb
|
125
154
|
- lib/et/operating_system.rb
|
126
155
|
- lib/et/runner.rb
|
156
|
+
- lib/et/submission_file_list.rb
|
127
157
|
- lib/et/version.rb
|
128
158
|
- spec/cli/get_lesson_spec.rb
|
129
159
|
- spec/cli/init_spec.rb
|
130
160
|
- spec/cli/list_lessons_spec.rb
|
131
161
|
- spec/cli/run_exercise_test_suite_spec.rb
|
132
162
|
- spec/cli/submit_lesson_spec.rb
|
163
|
+
- spec/data/bloated-challenge/.lesson.yml
|
164
|
+
- spec/data/bloated-challenge/bloated-challenge.md
|
165
|
+
- spec/data/bloated-challenge/node_modules/boo/somefile.js
|
166
|
+
- spec/data/bloated-challenge/problem.rb
|
167
|
+
- spec/data/bloated-challenge/rando_folder/rando_file.js
|
133
168
|
- spec/data/challenge.json
|
134
169
|
- spec/data/lessons.json
|
135
170
|
- spec/data/sample-challenge/.lesson.yml
|
@@ -145,8 +180,10 @@ files:
|
|
145
180
|
- spec/lib/challenge_spec.rb
|
146
181
|
- spec/lib/config_spec.rb
|
147
182
|
- spec/lib/exercise_spec.rb
|
183
|
+
- spec/lib/fallback_connection_spec.rb
|
148
184
|
- spec/lib/lesson_spec.rb
|
149
185
|
- spec/lib/operating_system_spec.rb
|
186
|
+
- spec/lib/submission_file_list_spec.rb
|
150
187
|
- spec/spec_helper.rb
|
151
188
|
- spec/support/helpers/archive_helper.rb
|
152
189
|
- spec/support/helpers/output_catcher.rb
|
@@ -173,7 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
210
|
version: '0'
|
174
211
|
requirements: []
|
175
212
|
rubyforge_project:
|
176
|
-
rubygems_version: 2.4.5
|
213
|
+
rubygems_version: 2.4.5.1
|
177
214
|
signing_key:
|
178
215
|
specification_version: 4
|
179
216
|
summary: Command-line interface for the event horizon.
|