hockeyapp 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|