keybase-core 0.1.2
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 +19 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +23 -0
- data/README.md +137 -0
- data/Rakefile +20 -0
- data/keybase.gemspec +25 -0
- data/lib/keybase.rb +49 -0
- data/lib/keybase/crypto.rb +20 -0
- data/lib/keybase/error.rb +37 -0
- data/lib/keybase/models/dump.rb +33 -0
- data/lib/keybase/models/user.rb +168 -0
- data/lib/keybase/request/base.rb +32 -0
- data/lib/keybase/request/dump/all.rb +10 -0
- data/lib/keybase/request/dump/latest.rb +10 -0
- data/lib/keybase/request/key/add.rb +13 -0
- data/lib/keybase/request/key/revoke.rb +14 -0
- data/lib/keybase/request/root/get_salt_and_login_session.rb +12 -0
- data/lib/keybase/request/root/login.rb +14 -0
- data/lib/keybase/request/sig/post_auth.rb +13 -0
- data/lib/keybase/request/user/lookup.rb +10 -0
- data/lib/keybase/response.rb +14 -0
- data/lib/keybase/token_store.rb +32 -0
- data/test/all.rb +6 -0
- data/test/fixtures/example_dump.json +9 -0
- data/test/fixtures/example_user.json +50 -0
- data/test/integration/dumps_test.rb +23 -0
- data/test/integration/keys_test.rb +26 -0
- data/test/integration/login_users_test.rb +18 -0
- data/test/integration/lookup_users_test.rb +42 -0
- data/test/integration_test_helper.rb +6 -0
- data/test/test_helper.rb +17 -0
- data/test/unit/lib/keybase/crypto_test.rb +45 -0
- data/test/unit/lib/keybase/error_test.rb +32 -0
- data/test/unit/lib/keybase/models/dump_test.rb +31 -0
- data/test/unit/lib/keybase/models/user_test.rb +136 -0
- data/test/unit/lib/keybase/requests/base_test.rb +60 -0
- data/test/unit/lib/keybase/requests/dump/all_test.rb +17 -0
- data/test/unit/lib/keybase/requests/dump/latest_test.rb +17 -0
- data/test/unit/lib/keybase/requests/key/add_test.rb +19 -0
- data/test/unit/lib/keybase/requests/key/revoke_test.rb +19 -0
- data/test/unit/lib/keybase/requests/root/get_salt_test.rb +23 -0
- data/test/unit/lib/keybase/requests/root/login_test.rb +19 -0
- data/test/unit/lib/keybase/requests/sig/post_auth_test.rb +19 -0
- data/test/unit/lib/keybase/requests/user/lookup_test.rb +17 -0
- data/test/unit/lib/keybase/response_test.rb +23 -0
- data/test/unit/lib/keybase/token_store_test.rb +25 -0
- data/test/unit/lib/keybase_test.rb +25 -0
- data/test/vcr_cassettes/dumps.yml +49418 -0
- data/test/vcr_cassettes/keys.yml +349 -0
- data/test/vcr_cassettes/user_login.yml +176 -0
- data/test/vcr_cassettes/user_lookup_foo.yml +89 -0
- data/test/vcr_cassettes/user_lookup_invalid.yml +52 -0
- data/test/vcr_cassettes/user_lookup_missing.yml +52 -0
- data/test/vcr_cassettes/user_lookup_not_found.yml +55 -0
- metadata +200 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
module Keybase
|
2
|
+
# A Keybase user containing all attributes you have permission to see
|
3
|
+
class User
|
4
|
+
|
5
|
+
attr_reader :id, :basics, :invitation_stats, :profile, :emails,
|
6
|
+
:public_keys, :private_keys
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@id = params['id']
|
10
|
+
@invitation_stats = OpenStruct.new(params['invitation_stats'])
|
11
|
+
|
12
|
+
set_basics(params['basics']) if params['basics']
|
13
|
+
set_profile(params['profile']) if params['profile']
|
14
|
+
set_emails(params['emails']) if params['emails']
|
15
|
+
set_public_keys(params['public_keys']) if params['public_keys']
|
16
|
+
set_private_keys(params['private_keys']) if params['private_keys']
|
17
|
+
end
|
18
|
+
|
19
|
+
# Lookup a user on Keybase
|
20
|
+
#
|
21
|
+
# @param [String] username the username of the user you are searching for
|
22
|
+
# @raise [Keybase::UserNotFoundError] if the user is not found
|
23
|
+
# @raise [Keybase::InputError] if the username is empty or invalid
|
24
|
+
# @return [Keybase::Model::User] the user, if they exist
|
25
|
+
def self.lookup(username)
|
26
|
+
new(Request::User.lookup(username))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Login to Keybase
|
30
|
+
#
|
31
|
+
# @param [String] email_or_username the email or username of the account
|
32
|
+
# @param [String] passphrase the passphrase for the account
|
33
|
+
# @raise [Keybase::UserNotFoundError] if the user is not found
|
34
|
+
# @raise [Keybase::InputError] if the submitted parameters are empty or invalid
|
35
|
+
# @raise [Keybase::BadPasswordError] if the submitted passphrase is incorrect
|
36
|
+
# @return [Keybase::Model::User] the user, if login is successful
|
37
|
+
def self.login(email_or_username, passphrase)
|
38
|
+
salt, login_session = Request::Root.get_salt_and_login_session(email_or_username)
|
39
|
+
pwh = Crypto.scrypt(passphrase, salt)
|
40
|
+
hmac_pwh = Crypto.hmac_sha512(pwh, login_session)
|
41
|
+
response = Request::Root.login(email_or_username, hmac_pwh, login_session)
|
42
|
+
return new(response['me'])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Post a self-signed authentication certificate to Keybase
|
46
|
+
#
|
47
|
+
# This requires login first.
|
48
|
+
#
|
49
|
+
# The payload of the signature should take the form of other keybase signatures,
|
50
|
+
# like self-signing keys, or proving ownership of remote accounts.
|
51
|
+
#
|
52
|
+
# An example looks like:
|
53
|
+
#
|
54
|
+
# {
|
55
|
+
# "body": {
|
56
|
+
# "key": {
|
57
|
+
# "fingerprint": "da99a6ebeca98b14d944cb6e1ca9bfeab344f0fc",
|
58
|
+
# "host": "keybase.io",
|
59
|
+
# "key_id": "1ca9bfeab344f0fc",
|
60
|
+
# "uid": "15a9e2826313eaf005291a1ae00c3f00",
|
61
|
+
# "username": "taco422107"
|
62
|
+
# },
|
63
|
+
# "nonce": null,
|
64
|
+
# "type": "auth",
|
65
|
+
# "version": 1
|
66
|
+
# },
|
67
|
+
# "ctime": 1386537779,
|
68
|
+
# "expire_in": 86400,
|
69
|
+
# "tag": "signature"
|
70
|
+
# }
|
71
|
+
#
|
72
|
+
# The client can provide an optional nonce to randomize the signatures. The server
|
73
|
+
# will check the signature for validatity, and on success, will return an auth_token,
|
74
|
+
# which is the SHA-256 hash of the full signature body, from the "---- BEGIN" all the
|
75
|
+
# way through to the ---- END PGP MESSAGE ----.
|
76
|
+
#
|
77
|
+
# @param [String] sig the whole certificate contents
|
78
|
+
# @raise [Keybase::InputError] if the certificate is empty or invalid
|
79
|
+
# @raise [Keybase::BadSessionError] if the session is not valid
|
80
|
+
# @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
|
81
|
+
# @return [String] the authentication token
|
82
|
+
def post_auth(sig)
|
83
|
+
Request::Sig.post_auth(basics.username, sig)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add a new public key to Keybase
|
87
|
+
#
|
88
|
+
# This requires login first.
|
89
|
+
#
|
90
|
+
# @param [String] key the public key
|
91
|
+
# @raise [Keybase::InputError] if the key is empty or invalid
|
92
|
+
# @raise [Keybase::BadSessionError] if the session is not valid
|
93
|
+
# @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
|
94
|
+
# @return [String] The Key ID for the uploaded key
|
95
|
+
def add_public_key(key)
|
96
|
+
Request::Key.add(public_key: key)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add a new private key to Keybase
|
100
|
+
#
|
101
|
+
# This requires login first.
|
102
|
+
#
|
103
|
+
# @param [String] key the private key
|
104
|
+
# @raise [Keybase::InputError] if the key is empty or invalid
|
105
|
+
# @raise [Keybase::BadSessionError] if the session is not valid
|
106
|
+
# @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
|
107
|
+
# @return [String] The Key ID for the uploaded key
|
108
|
+
def add_private_key(key)
|
109
|
+
Request::Key.add(private_key: key)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Revoke a key from Keybase
|
113
|
+
#
|
114
|
+
# This requires login first.
|
115
|
+
#
|
116
|
+
# Currently the key is simply deleted - full revokation is due in later
|
117
|
+
# revisions of the API.
|
118
|
+
#
|
119
|
+
# @param [String] kid the key id to be revoked
|
120
|
+
# @raise [Keybase::InputError] if the key id is empty or invalid
|
121
|
+
# @raise [Keybase::BadSessionError] if the session is not valid
|
122
|
+
# @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
|
123
|
+
# @return [Boolean] success
|
124
|
+
def revoke_key(kid)
|
125
|
+
Request::Key.revoke(kid)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def set_basics(params)
|
131
|
+
@basics = OpenStruct.new(params.merge(created_at: nil, updated_at: nil))
|
132
|
+
@basics.created_at = Time.at(@basics.ctime).to_datetime if @basics.ctime
|
133
|
+
@basics.updated_at = Time.at(@basics.mtime).to_datetime if @basics.mtime
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_profile(params)
|
137
|
+
@profile = OpenStruct.new(params.merge(updated_at: nil))
|
138
|
+
@profile.updated_at = Time.at(@profile.mtime).to_datetime if @profile.mtime
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_emails(params)
|
142
|
+
@emails = OpenStruct.new(params)
|
143
|
+
params.each do |k,v|
|
144
|
+
v.merge!('is_verified?' => (v['is_verified'] == 1 ? true : false))
|
145
|
+
@emails.send("#{k}=".to_sym, OpenStruct.new(v))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_public_keys(params)
|
150
|
+
@public_keys = OpenStruct.new(params)
|
151
|
+
params.each do |k,v|
|
152
|
+
v.merge!('created_at' => Time.at(v['ctime']).to_datetime)
|
153
|
+
v.merge!('updated_at' => Time.at(v['mtime']).to_datetime)
|
154
|
+
@public_keys.send("#{k}=".to_sym, OpenStruct.new(v))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_private_keys(params)
|
159
|
+
@private_keys = OpenStruct.new(params)
|
160
|
+
params.each do |k,v|
|
161
|
+
v.merge!('created_at' => Time.at(v['ctime']).to_datetime)
|
162
|
+
v.merge!('updated_at' => Time.at(v['mtime']).to_datetime)
|
163
|
+
@private_keys.send("#{k}=".to_sym, OpenStruct.new(v))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Keybase
|
2
|
+
# @private
|
3
|
+
module Request
|
4
|
+
API_BASE_URL = 'https://keybase.io'
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def self.get(url, params={})
|
8
|
+
Keybase::Response.new(conn.get(url, params)).body
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.post(url, params={})
|
12
|
+
response = Keybase::Response.new(conn.post(url, params))
|
13
|
+
TokenStore.cookie = response.cookie if response.cookie
|
14
|
+
response.body
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.conn
|
20
|
+
Faraday.new(:url => API_BASE_URL) do |faraday|
|
21
|
+
faraday.path_prefix = "/_/api/1.0"
|
22
|
+
faraday.request :url_encoded
|
23
|
+
faraday.headers['Cookie'] = TokenStore.cookie if TokenStore.cookie
|
24
|
+
faraday.headers['X-CSRF-Token'] = TokenStore.csrf if TokenStore.csrf
|
25
|
+
# faraday.response :logger
|
26
|
+
faraday.adapter Faraday.default_adapter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# POST /key/add.json
|
2
|
+
# public_key: "-----BEGIN PGP PUBLIC..."
|
3
|
+
# private_key: "hKRib2R5gqRwcml2gqRkY..."
|
4
|
+
# is_primary: true
|
5
|
+
module Keybase
|
6
|
+
module Request
|
7
|
+
class Key < Base
|
8
|
+
def self.add(params)
|
9
|
+
post('key/add.json', params.merge(is_primary: true))['kid']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# POST /key/revoke.json
|
2
|
+
# revocation_type: 0
|
3
|
+
# kid: "a140c70404a13370f7..."
|
4
|
+
module Keybase
|
5
|
+
module Request
|
6
|
+
class Key < Base
|
7
|
+
def self.revoke(kid)
|
8
|
+
post('key/revoke.json', revocation_type: 0,
|
9
|
+
csrf_token: TokenStore.csrf, kid: kid)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# GET /getsalt.json?email_or_username=EMAIL_OR_USERNAME
|
2
|
+
module Keybase
|
3
|
+
module Request
|
4
|
+
class Root < Base
|
5
|
+
def self.get_salt_and_login_session(email_or_username)
|
6
|
+
result = get('getsalt.json', email_or_username: email_or_username)
|
7
|
+
TokenStore.csrf = result['csrf_token']
|
8
|
+
return [result['salt'], result['login_session']]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# POST /login.json
|
2
|
+
# email_or_username: "chris"
|
3
|
+
# hmac_pwh: "38902fe098f..."
|
4
|
+
# login_session: "lgHZIwfee99..."
|
5
|
+
module Keybase
|
6
|
+
module Request
|
7
|
+
class Root < Base
|
8
|
+
def self.login(email_or_username, hmac_pwh, login_session)
|
9
|
+
post('login.json', email_or_username: email_or_username,
|
10
|
+
hmac_pwh: hmac_pwh, login_session: login_session)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# POST sig/post_auth.json
|
2
|
+
# sig : "----- BEGIN PGP MESSAGE ----- ..."
|
3
|
+
# email_or_username : "maxtaco"
|
4
|
+
module Keybase
|
5
|
+
module Request
|
6
|
+
class Sig < Base
|
7
|
+
def self.post_auth(email_or_username, sig)
|
8
|
+
post('sig/post_auth.json', email_or_username: email_or_username,
|
9
|
+
sig: sig)['auth_token']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Keybase
|
2
|
+
# @private
|
3
|
+
class Response
|
4
|
+
|
5
|
+
attr_reader :body, :cookie
|
6
|
+
|
7
|
+
def initialize(server_response)
|
8
|
+
@body = JSON.parse(server_response.body)
|
9
|
+
Error.raise_unless_successful(@body['status'])
|
10
|
+
@cookie = server_response.headers['Set-Cookie'] ? server_response.headers['Set-Cookie'] : nil
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Keybase
|
2
|
+
# @private
|
3
|
+
class TokenStore
|
4
|
+
|
5
|
+
attr_accessor :csrf, :cookie
|
6
|
+
|
7
|
+
def self.csrf
|
8
|
+
instance.csrf
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.csrf=(csrf)
|
12
|
+
instance.csrf = csrf
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.cookie
|
16
|
+
instance.cookie
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.cookie=(cookie)
|
20
|
+
instance.cookie = cookie
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.instance
|
24
|
+
@@instance ||= new
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize ; end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/test/all.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
{
|
2
|
+
"dump_id": "472eba4586993583b395b00e",
|
3
|
+
"full_data": "https://s3.amazonaws.com/keybase_data_dumps/2014-02-24-13-46-31-...json",
|
4
|
+
"full_data_sha256": "bea8863f1b17d35d7db4480197886e2f2d9f8deb0633c5f2adcc427541b79bd7",
|
5
|
+
"parent_dump_id": "c56698ea60027c2e9f98620e",
|
6
|
+
"changes_from_parent": "https://s3.amazonaws.com/keybase_data_dumps/2014-02-...json",
|
7
|
+
"changes_sha256": "88a248688299eb2fd25d1031d35abca8243ad5100fc614a213d300512fd2b634",
|
8
|
+
"ctime": 1393267591
|
9
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"id": "bf5f1650fc1361a",
|
3
|
+
"basics": {
|
4
|
+
"uid": "55c079bf5f1650fc1361a1c3a4709900",
|
5
|
+
"salt": "533a6842479b5e925856d1ce2a444...",
|
6
|
+
"username": "chris",
|
7
|
+
"ctime": 1383161061,
|
8
|
+
"mtime": 1383162061
|
9
|
+
},
|
10
|
+
"invitation_stats": {
|
11
|
+
"available": 3,
|
12
|
+
"used": 2,
|
13
|
+
"power": 1,
|
14
|
+
"open": 0
|
15
|
+
},
|
16
|
+
"profile": {
|
17
|
+
"full_name": "Chris Coyne",
|
18
|
+
"location": null,
|
19
|
+
"bio": "I am the terror that flaps in the night.",
|
20
|
+
"mtime": 1383247339
|
21
|
+
},
|
22
|
+
"emails": {
|
23
|
+
"primary": {
|
24
|
+
"email": "chris@okcupid.com",
|
25
|
+
"is_verified": 1
|
26
|
+
},
|
27
|
+
"secondary": {
|
28
|
+
"email": "chris@theinter.net",
|
29
|
+
"is_verified": 0
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"public_keys": {
|
33
|
+
"primary": {
|
34
|
+
"kid": "d028ac1542b24a5f77f123ba467873e3ce96a992570a",
|
35
|
+
"key_type": 1,
|
36
|
+
"bundle": "-----BEGIN PGP PUBLIC KEY BLOCK-----\\nVersion => Keybase OpenPGP JS 0.0.1\\nComment => https => //keybase.io\\n\\nxo0EUnlbBAEEALU9OLTEIA0h2o8NZc+0b9f8RaCa8nLfqm3cKNZ3aAV9iYLkeHnd\\nqhP99c+VTiB3Rtjeqf9hZAqKCIJlVrURPgN/mPRzRk5u4OmI/vw/HMn+3lBLwTJZ\\n+FQGEGvLvzBz9wEpcM3IKrXl70BJbq/uM3SP0fsu60/fIy4twEvYOS9JABEBAAHN\\nI2tleWJhc2UuaW8vY2hyaXMgPGNocmlzQGtleWJhc2UuaW8+wrMEEAEKAB0FAlJ5\\nWwQCGy8FCRLMAwADCwkHAxUKCAIeAQIXgAAKCRAb5d5e+VB/cndDA/0Rgsu84CpW\\nN2lwvjCr/MJM9MV8Y75NAl5SFArKksg1d4DYH40M0boJuHETk3AR60bHDRlrhS5/\\nUPm9+A3PAa4IRXKw967e/8nhH3o8mhstDRCMJUrNfo2so5JzFSgWQaflDta0kE/E\\nNlUEOBqmt9Y2f/elhuxiGa0GkoBpJja2ks6NBFJ5WwQBBACva+Om+XqIrBMyDO54\\nNptSiNnjEEJoFJ6hMCqR3S88Kj/Jzu3jTiY+sqOsCDqMOv83AiKWBEL7U28eqhb7\\nwUEOL9nbKOWsQVvCqtla8vgQBLPZ3kYxs1CfJCnaC/bT6abh8+OHECSTENmurSV0\\na22IlMNJrU+3DZ/A3nrca8GGcQARAQABwsCDBBgBCgAPBQJSeVsEBQkB4TOAAhsu\\nAKgJEBvl3l75UH9ynSAEGQEKAAYFAlJ5WwQACgkQl6sBE/Y073xyYwQAryX0fcCA\\nO/ZCZi3pC26CWUtu6Vr6uSl9VSIRQFqcNoZ7PIcjQwGif/iHbBSap1wDkRBlMKkd\\n76vJi67z3etGZ5bJUb4iG/IJUOx6Yn2RGFtyV6N3t1w84dafqaR3cuOsT+UPYSIB\\njxgUPqfN68hqhW0ZBjXCva/P3dPhLulm6zY1AgQAqNDxGPui5OfNBzczq2raoF8E\\nyVJ1gRtP1eGJv6LZ01ABCSJg4h+tgKZuVHIX+i+rcqOY3ABEYWrhfjJBuVNPkM2I\\nFoMllSH7/ywNv8k8PvNBtybqrMiIUJyGrtLvjKhxmD+yxxvPcHWbTkearCycY4QW\\nVlLXYoBhF0AWKph5v/M=\\n=3FfV\\n-----END PGP PUBLIC KEY BLOCK-----\\n",
|
37
|
+
"mtime": 1383685212,
|
38
|
+
"ctime": 1383685212
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"private_keys": {
|
42
|
+
"primary": {
|
43
|
+
"kid": "d028ac1542b24a5f77f123ba467873e3ce96a992570a",
|
44
|
+
"key_type": 2,
|
45
|
+
"bundle": "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",
|
46
|
+
"mtime": 1383685212,
|
47
|
+
"ctime": 1383685212
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require_relative '../integration_test_helper'
|
3
|
+
|
4
|
+
module Keybase
|
5
|
+
class DumpsIntegrationTest < Minitest::Test
|
6
|
+
|
7
|
+
def setup
|
8
|
+
VCR.use_cassette('dumps') do
|
9
|
+
@all = Keybase.dump_all
|
10
|
+
@latest = Keybase.dump_latest
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_latest_dump_object_has_expected_fields
|
15
|
+
assert @latest.respond_to? :id
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_first_dump_object_has_expected_fields
|
19
|
+
assert @all[0].respond_to? :id
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|