hockeyapp 0.0.15
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 +15 -0
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/hockeyapp.gemspec +33 -0
- data/lib/hockeyapp.rb +25 -0
- data/lib/hockeyapp/config.rb +18 -0
- data/lib/hockeyapp/models/active_model_compliance.rb +10 -0
- data/lib/hockeyapp/models/app.rb +118 -0
- data/lib/hockeyapp/models/crash.rb +44 -0
- data/lib/hockeyapp/models/crash_group.rb +36 -0
- data/lib/hockeyapp/models/version.rb +91 -0
- data/lib/hockeyapp/services/android_urls.rb +30 -0
- data/lib/hockeyapp/services/ios_urls.rb +35 -0
- data/lib/hockeyapp/ws/client.rb +72 -0
- data/lib/hockeyapp/ws/ws.rb +96 -0
- data/spec/config_spec.rb +35 -0
- data/spec/hockey_app_spec.rb +13 -0
- data/spec/models/app_spec.rb +143 -0
- data/spec/models/crash_group_spec.rb +53 -0
- data/spec/models/crash_spec.rb +77 -0
- data/spec/models/version_spec.rb +85 -0
- data/spec/support/active_model_lint.rb +19 -0
- data/spec/support/fake_ws.rb +54 -0
- data/spec/support/responses/app.json +83 -0
- data/spec/support/responses/app_versions.json +34 -0
- data/spec/support/responses/apps.json +22 -0
- data/spec/support/responses/crash_groups.json +43 -0
- data/spec/support/responses/crashes.json +45 -0
- data/spec/support/responses/new_app.json +12 -0
- data/spec/support/responses/new_version.json +13 -0
- data/spec/support/rspec_helper.rb +18 -0
- data/spec/ws/client_spec.rb +153 -0
- data/spec/ws/ws_spec.rb +32 -0
- metadata +197 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module HockeyApp
|
2
|
+
class AndroidAppUrls
|
3
|
+
def initialize app
|
4
|
+
@app=app
|
5
|
+
end
|
6
|
+
|
7
|
+
def direct_download_url
|
8
|
+
"https://rink.hockeyapp.net/api/2/apps/#{@app.public_identifier}?format=apk"
|
9
|
+
end
|
10
|
+
|
11
|
+
def install_url
|
12
|
+
direct_download_url
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class AndroidVersionUrls
|
18
|
+
def initialize version
|
19
|
+
@version=version
|
20
|
+
end
|
21
|
+
|
22
|
+
def direct_download_url
|
23
|
+
"https://rink.hockeyapp.net/api/2/apps/#{@version.app.public_identifier}/app_versions/#{@version.id.to_s}?format=apk"
|
24
|
+
end
|
25
|
+
|
26
|
+
def install_url
|
27
|
+
direct_download_url
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module HockeyApp
|
2
|
+
class IOSAppUrls
|
3
|
+
def initialize app
|
4
|
+
@app=app
|
5
|
+
end
|
6
|
+
def download_url
|
7
|
+
"https://rink.hockeyapp.net/apps/#{@app.public_identifier}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def direct_download_url
|
11
|
+
@app.last_version.direct_download_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def install_url
|
15
|
+
@app.last_version.install_url
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class IOSVersionUrls
|
21
|
+
|
22
|
+
def initialize version
|
23
|
+
@version = version
|
24
|
+
end
|
25
|
+
|
26
|
+
def direct_download_url
|
27
|
+
"https://rink.hockeyapp.net/api/2/apps/#{@version.app.public_identifier}/app_versions/#{@version.id.to_s}?format=ipa"
|
28
|
+
end
|
29
|
+
|
30
|
+
def install_url
|
31
|
+
location = "https://rink.hockeyapp.net/api/2/apps/#{@version.app.public_identifier}/app_versions/#{@version.id.to_s}?format=plist"
|
32
|
+
"itms-services://?action=download-manifest&url=#{CGI::escape(location)}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module HockeyApp
|
2
|
+
class Client
|
3
|
+
|
4
|
+
def initialize ws
|
5
|
+
@ws = ws
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_apps
|
9
|
+
apps_hash = ws.get_apps
|
10
|
+
assert_success apps_hash
|
11
|
+
apps_hash["apps"].map{|app_hash|App.from_hash(app_hash, self)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_crashes app
|
15
|
+
crashes_hash = ws.get_crashes app.public_identifier
|
16
|
+
assert_success crashes_hash
|
17
|
+
crashes_hash["crashes"].map{|crash_hash|Crash.from_hash(crash_hash, app, self)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_crash_groups app
|
21
|
+
crash_groups_hash = ws.get_crash_groups app.public_identifier
|
22
|
+
assert_success crash_groups_hash
|
23
|
+
crash_groups_hash["crash_reasons"].map{|reason_hash|CrashGroup.from_hash(reason_hash, app, self)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_crash_log crash
|
27
|
+
ws.get_crash_log crash.app.public_identifier, crash.id
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_crash_description crash
|
31
|
+
ws.get_crash_description crash.app.public_identifier, crash.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_versions app
|
35
|
+
versions_hash = ws.get_versions app.public_identifier
|
36
|
+
versions_hash["app_versions"].map{|version_hash|Version.from_hash(version_hash, app, self)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_new_version version
|
40
|
+
app_id = version.app.public_identifier
|
41
|
+
ipa = version.ipa
|
42
|
+
raise "There must be an executable file" if ipa.nil?
|
43
|
+
version_hash = ws.post_new_version(app_id, ipa, version.dsym, version.notes, version.notes_type, version.notify, version.status)
|
44
|
+
raise version_hash['errors'].map{|e|e.to_s}.join("\n") unless version_hash['errors'].nil?
|
45
|
+
Version.from_hash(version_hash, version.app, self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_app app
|
49
|
+
resp = ws.remove_app app.public_identifier
|
50
|
+
raise "unexpected response" if resp.code != 200
|
51
|
+
resp.code == 200
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_app file_ipa
|
55
|
+
resp = ws.post_new_app(file_ipa)
|
56
|
+
raise resp['errors'].map{|e|e.to_s}.join("\n") unless resp['errors'].nil?
|
57
|
+
App.from_hash(resp, self)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :ws
|
65
|
+
|
66
|
+
def assert_success hash
|
67
|
+
status = hash["status"]
|
68
|
+
raise "Bad Status : #{status}" unless status == "success"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'httmultiparty'
|
2
|
+
|
3
|
+
module HockeyApp
|
4
|
+
class WS
|
5
|
+
include HTTMultiParty
|
6
|
+
base_uri 'https://rink.hockeyapp.net/api/2'
|
7
|
+
headers 'Accept' => 'application/json'
|
8
|
+
format :json
|
9
|
+
|
10
|
+
|
11
|
+
def initialize (options = {})
|
12
|
+
@options = Config.to_h.merge(options)
|
13
|
+
raise "No API Token Given" if (@options[:token].nil?)
|
14
|
+
self.class.headers 'X-HockeyAppToken' => @options[:token]
|
15
|
+
self.class.base_uri @options[:base_uri] if @options[:base_uri].present?
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def get_apps
|
20
|
+
self.class.get '/apps'
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def get_crashes app_id, options = {}
|
25
|
+
self.class.get "/apps/#{app_id}/crashes", options
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_crash_groups app_id, options = {}
|
29
|
+
self.class.get "/apps/#{app_id}/crash_reasons", options
|
30
|
+
end
|
31
|
+
|
32
|
+
# this is damn not thread safe !
|
33
|
+
def get_crash_log app_id, crash_id, options = {}
|
34
|
+
self.class.format :plain
|
35
|
+
log = self.class.get "/apps/#{app_id}/crashes/#{crash_id}?format=log", options
|
36
|
+
self.class.format :json
|
37
|
+
log
|
38
|
+
end
|
39
|
+
|
40
|
+
# this is damn not thread safe !
|
41
|
+
def get_crash_description app_id, crash_id, options = {}
|
42
|
+
self.class.format :plain
|
43
|
+
description = self.class.get "/apps/#{app_id}/crashes/#{crash_id}?format=text", options
|
44
|
+
self.class.format :json
|
45
|
+
description
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_versions app_id, options = {}
|
49
|
+
self.class.get "/apps/#{app_id}/app_versions", options
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_new_version(
|
53
|
+
app_id,
|
54
|
+
ipa,
|
55
|
+
dsym=nil,
|
56
|
+
notes="New version",
|
57
|
+
notes_type=Version::NOTES_TYPES_TO_SYM.invert[:textile],
|
58
|
+
notify=Version::NOTIFY_TO_BOOL.invert[false],
|
59
|
+
status=Version::STATUS_TO_SYM.invert[:allow]
|
60
|
+
)
|
61
|
+
params = {
|
62
|
+
:ipa => ipa ,
|
63
|
+
:dsym => dsym ,
|
64
|
+
:notes => notes,
|
65
|
+
:notes_type => notes_type,
|
66
|
+
:notify => notify,
|
67
|
+
:status => status
|
68
|
+
}
|
69
|
+
params.reject!{|_,v|v.nil?}
|
70
|
+
self.class.post "/apps/#{app_id}/app_versions", :body => params
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def remove_app app_id
|
75
|
+
self.class.format :plain
|
76
|
+
response = self.class.delete "/apps/#{app_id}"
|
77
|
+
self.class.format :json
|
78
|
+
response
|
79
|
+
end
|
80
|
+
|
81
|
+
def post_new_app(file_ipa,
|
82
|
+
notes="New app",
|
83
|
+
notes_type=App::NOTES_TYPES_TO_SYM.invert[:textile],
|
84
|
+
notify=App::NOTIFY_TO_BOOL.invert[false],
|
85
|
+
status=App::STATUS_TO_SYM.invert[:allow])
|
86
|
+
params = {
|
87
|
+
:ipa => file_ipa,
|
88
|
+
:notes => notes,
|
89
|
+
:notes_type => notes_type,
|
90
|
+
:notify => notify,
|
91
|
+
:status => status
|
92
|
+
}
|
93
|
+
self.class.post "/apps", :body => params
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'support/rspec_helper'
|
2
|
+
|
3
|
+
describe HockeyApp::Config do
|
4
|
+
|
5
|
+
it "can be set throug configure method" do
|
6
|
+
HockeyApp::Config.configure do |config|
|
7
|
+
config.token = "ABCDEF"
|
8
|
+
end
|
9
|
+
|
10
|
+
HockeyApp::Config.token.should == "ABCDEF"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can store a token" do
|
14
|
+
HockeyApp::Config.token = "ABCDEF"
|
15
|
+
HockeyApp::Config.token.should == "ABCDEF"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can store a base_uri" do
|
19
|
+
HockeyApp::Config.base_uri = "foo"
|
20
|
+
HockeyApp::Config.base_uri.should == "foo"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can transform into a hash" do
|
24
|
+
HockeyApp::Config.configure do |config|
|
25
|
+
config.token = "ABCDEF"
|
26
|
+
config.base_uri = "foobar"
|
27
|
+
end
|
28
|
+
hash = HockeyApp::Config.to_h
|
29
|
+
hash.should == {
|
30
|
+
:token => "ABCDEF",
|
31
|
+
:base_uri => "foobar"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'support/rspec_helper'
|
2
|
+
|
3
|
+
describe HockeyApp do
|
4
|
+
before :each do
|
5
|
+
HockeyApp::Config.configure do |config|
|
6
|
+
config.token = "ABCDEF"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
it "can build a ws client" do
|
10
|
+
client = HockeyApp.build_client
|
11
|
+
client.should be_a HockeyApp::Client
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require_relative '../support/rspec_helper'
|
2
|
+
|
3
|
+
describe HockeyApp::App do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
h = {
|
7
|
+
"bundle_identifier" => "de.codenauts.hockeytest.beta",
|
8
|
+
"device_family" => "iPhone/iPod",
|
9
|
+
"public_identifier" => "1234567890abcdef1234567890abcdef",
|
10
|
+
"company" => "some company",
|
11
|
+
"release_type" => 0,
|
12
|
+
"platform" => "iOS",
|
13
|
+
"title" => "Hockey Test",
|
14
|
+
"role" => 0,
|
15
|
+
"status" => 2,
|
16
|
+
"minimum_os_version" => "4.0",
|
17
|
+
"owner" => "John Doe"
|
18
|
+
}
|
19
|
+
@client = HockeyApp::Client.new(HockeyApp::FakeWS.new)
|
20
|
+
@app = HockeyApp::App.from_hash h, @client
|
21
|
+
@model = @app
|
22
|
+
end
|
23
|
+
|
24
|
+
it_behaves_like "ActiveModel"
|
25
|
+
|
26
|
+
it "can give me info about my application" do
|
27
|
+
@app.bundle_identifier.should == "de.codenauts.hockeytest.beta"
|
28
|
+
@app.device_family.should == "iPhone/iPod"
|
29
|
+
@app.public_identifier.should == "1234567890abcdef1234567890abcdef"
|
30
|
+
@app.company.should == "some company"
|
31
|
+
@app.release_type.should == 0
|
32
|
+
@app.platform.should == "iOS"
|
33
|
+
@app.title.should == "Hockey Test"
|
34
|
+
@app.role.should == 0
|
35
|
+
@app.status.should == 2
|
36
|
+
@app.minimum_os_version.should == "4.0"
|
37
|
+
@app.owner.should == "John Doe"
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "will call client once when asked for crashes" do
|
42
|
+
@client.should_receive(:get_crashes).with(@app).and_return([])
|
43
|
+
@app.crashes
|
44
|
+
@client.should_not_receive(:get_crashes).with(@app)
|
45
|
+
@app.crashes
|
46
|
+
end
|
47
|
+
|
48
|
+
it "will call client once when asked for crash reasons" do
|
49
|
+
@client.should_receive(:get_crash_groups).with(@app).and_return([])
|
50
|
+
@app.crash_reasons
|
51
|
+
@client.should_not_receive(:get_crash_groups).with(@app)
|
52
|
+
@app.crash_reasons
|
53
|
+
end
|
54
|
+
|
55
|
+
it "will call client once when asked for versions" do
|
56
|
+
@client.should_receive(:get_versions).with(@app).and_return([])
|
57
|
+
@app.versions
|
58
|
+
@client.should_not_receive(:get_versions).with(@app)
|
59
|
+
@app.versions
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can generate a download url" do
|
63
|
+
@app.download_url.should == "https://rink.hockeyapp.net/apps/0873e2b98ad046a92c170a243a8515f6/app_versions/208"
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
it "can generate a direct download url for Android" do
|
69
|
+
@app.platform = "Android"
|
70
|
+
@app.direct_download_url.should == "https://rink.hockeyapp.net/api/2/apps/1234567890abcdef1234567890abcdef?format=apk"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "can generate a direct download url for iOS" do
|
74
|
+
@app.platform = "iOS"
|
75
|
+
@app.direct_download_url.should == "https://rink.hockeyapp.net/api/2/apps/1234567890abcdef1234567890abcdef/app_versions/208?format=ipa"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "can generate an install url for iOS" do
|
79
|
+
@app.install_url.should == "itms-services://?action=download-manifest&url=https%3A%2F%2Frink.hockeyapp.net%2Fapi%2F2%2Fapps%2F1234567890abcdef1234567890abcdef%2Fapp_versions%2F208%3Fformat%3Dplist"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can generate an icon url for iOS" do
|
83
|
+
@app.icon.should == "https://rink.hockeyapp.net/api/2/apps/1234567890abcdef1234567890abcdef?format=png"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can generate an icon url for Android" do
|
87
|
+
@app.platform = "Android"
|
88
|
+
@app.icon.should == "https://rink.hockeyapp.net/api/2/apps/1234567890abcdef1234567890abcdef?format=png"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can generate an install url for Android" do
|
92
|
+
@app.platform = "Android"
|
93
|
+
@app.install_url.should == @app.direct_download_url
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
describe "#create_version" do
|
98
|
+
it "will create a new version instance and pass it to the webservice" do
|
99
|
+
release_notes = "New version from automated test"
|
100
|
+
binary_file = double('file')
|
101
|
+
fake_version = double('version')
|
102
|
+
HockeyApp::Version.should_receive(:new).with(@app, @client).and_return(fake_version)
|
103
|
+
fake_version.should_receive(:ipa=).with(binary_file)
|
104
|
+
fake_version.should_receive(:notes=).with(release_notes)
|
105
|
+
@client.should_receive(:post_new_version).with(fake_version)
|
106
|
+
@app.create_version(binary_file, release_notes)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#remove_app", :js => true do
|
112
|
+
it "will remove an app from hockeyapp", :driver => :webkit do
|
113
|
+
@client.should_receive(:remove_app).with(@app).and_return(true)
|
114
|
+
@app.remove
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#last_version" do
|
119
|
+
|
120
|
+
it "sorts the versions" do
|
121
|
+
versions = double("versions")
|
122
|
+
@app.should_receive(:versions).and_return(versions)
|
123
|
+
versions.should_receive(:sort_by).and_return([])
|
124
|
+
@app.last_version
|
125
|
+
end
|
126
|
+
|
127
|
+
context "there are 3 versions for this app" do
|
128
|
+
before :each do
|
129
|
+
@version1 = double("Version1", :version => "5")
|
130
|
+
@version2 = double("Version2", :version => "10")
|
131
|
+
@version3 = double("Version3", :version => "6")
|
132
|
+
@versions = [@version1, @version2, @version3]
|
133
|
+
|
134
|
+
end
|
135
|
+
it "returns the last version" do
|
136
|
+
@app.should_receive(:versions).and_return(@versions)
|
137
|
+
@app.last_version.version.should == "10"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative '../support/rspec_helper'
|
2
|
+
|
3
|
+
describe HockeyApp::CrashGroup do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
h = {
|
7
|
+
"file" => "AbstractParser.java",
|
8
|
+
"reason" => "java.lang.RuntimeException: An error occured while executing doInBackground()",
|
9
|
+
"status" => 1,
|
10
|
+
"id" => 135837,
|
11
|
+
"class" => "com.tapptic.common.parser.AbstractParser",
|
12
|
+
"bundle_version" => "2",
|
13
|
+
"last_crash_at" => "2011-12-15T20:07:11Z",
|
14
|
+
"app_version_id" => 2,
|
15
|
+
"line" => "48",
|
16
|
+
"updated_at" => "2012-01-03T13:22:01Z",
|
17
|
+
"bundle_short_version" => "0.2",
|
18
|
+
"method" => "parse",
|
19
|
+
"number_of_crashes" => 2,
|
20
|
+
"fixed" => true,
|
21
|
+
"created_at" => "2011-12-15T20:06:49Z",
|
22
|
+
"app_id" => 9999
|
23
|
+
}
|
24
|
+
@client = HockeyApp::Client.new(HockeyApp::FakeWS.new)
|
25
|
+
@app = HockeyApp::App.from_hash( {"public_identifier" => "91423bc5519dd2462513abbb54598959"}, @client)
|
26
|
+
@crash_group = HockeyApp::CrashGroup.from_hash h, @app, @client
|
27
|
+
@model = @crash_group
|
28
|
+
end
|
29
|
+
|
30
|
+
it_behaves_like "ActiveModel"
|
31
|
+
|
32
|
+
it "can give me info about the crash group" do
|
33
|
+
@crash_group.file.should == "AbstractParser.java"
|
34
|
+
@crash_group.reason.should == "java.lang.RuntimeException: An error occured while executing doInBackground()"
|
35
|
+
@crash_group.status.should == 1
|
36
|
+
@crash_group.id.should == 135837
|
37
|
+
@crash_group.crash_class.should == "com.tapptic.common.parser.AbstractParser"
|
38
|
+
@crash_group.bundle_version.should == "2"
|
39
|
+
@crash_group.last_crash_at.should == "2011-12-15T20:07:11Z"
|
40
|
+
@crash_group.app_version_id.should == 2
|
41
|
+
@crash_group.updated_at.should == "2012-01-03T13:22:01Z"
|
42
|
+
@crash_group.bundle_short_version.should == "0.2"
|
43
|
+
@crash_group.method.should == "parse"
|
44
|
+
@crash_group.number_of_crashes.should == 2
|
45
|
+
@crash_group.fixed.should == true
|
46
|
+
@crash_group.created_at.should == "2011-12-15T20:06:49Z"
|
47
|
+
@crash_group.app_id.should == 9999
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
|