togglv8-ng 1.4.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 +7 -0
- data/.codeclimate.yml +16 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -0
- data/.rdoc_options +23 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +101 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +160 -0
- data/Rakefile +6 -0
- data/lib/logging.rb +38 -0
- data/lib/reportsv2.rb +177 -0
- data/lib/togglv8/clients.rb +37 -0
- data/lib/togglv8/connection.rb +99 -0
- data/lib/togglv8/dashboard.rb +14 -0
- data/lib/togglv8/project_users.rb +32 -0
- data/lib/togglv8/projects.rb +112 -0
- data/lib/togglv8/tags.rb +25 -0
- data/lib/togglv8/tasks.rb +55 -0
- data/lib/togglv8/time_entries.rb +114 -0
- data/lib/togglv8/togglv8.rb +55 -0
- data/lib/togglv8/users.rb +74 -0
- data/lib/togglv8/version.rb +4 -0
- data/lib/togglv8/workspaces.rb +47 -0
- data/lib/togglv8.rb +13 -0
- data/spec/lib/reportsv2_spec.rb +254 -0
- data/spec/lib/togglv8/clients_spec.rb +145 -0
- data/spec/lib/togglv8/dashboard_spec.rb +31 -0
- data/spec/lib/togglv8/projects_spec.rb +111 -0
- data/spec/lib/togglv8/tags_spec.rb +54 -0
- data/spec/lib/togglv8/tasks_spec.rb +100 -0
- data/spec/lib/togglv8/time_entries_spec.rb +425 -0
- data/spec/lib/togglv8/users_spec.rb +82 -0
- data/spec/lib/togglv8/workspaces_spec.rb +45 -0
- data/spec/lib/togglv8_spec.rb +88 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/togglv8_spec_helper.rb +75 -0
- data/togglv8.gemspec +33 -0
- metadata +223 -0
@@ -0,0 +1,425 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
describe 'Time Entries' do
|
4
|
+
before :all do
|
5
|
+
@toggl = TogglV8::API.new(Testing::API_TOKEN)
|
6
|
+
@workspaces = @toggl.workspaces
|
7
|
+
@workspace_id = @workspaces.first['id']
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'CRUD time entry' do
|
11
|
+
before :each do
|
12
|
+
time_entry_info = {
|
13
|
+
'wid' => @workspace_id,
|
14
|
+
'start' => @toggl.iso8601(DateTime.now),
|
15
|
+
'duration' => 77
|
16
|
+
}
|
17
|
+
|
18
|
+
@expected = time_entry_info.clone
|
19
|
+
|
20
|
+
@time_entry = @toggl.create_time_entry(time_entry_info)
|
21
|
+
end
|
22
|
+
|
23
|
+
after :each do
|
24
|
+
@toggl.delete_time_entry(@time_entry['id'])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'creates a time entry' do
|
28
|
+
expect(@time_entry).to include(@expected)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'requires a workspace, project, or task to create' do
|
32
|
+
time_entry_info = {
|
33
|
+
'start' => @toggl.iso8601(DateTime.now),
|
34
|
+
'duration' => 77
|
35
|
+
}
|
36
|
+
|
37
|
+
expect {
|
38
|
+
@toggl.create_time_entry(time_entry_info)
|
39
|
+
}.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'gets a time entry' do
|
43
|
+
retrieved_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
44
|
+
|
45
|
+
['start', 'stop'].each do |key|
|
46
|
+
expect(retrieved_time_entry[key]).to eq_ts @time_entry[key]
|
47
|
+
retrieved_time_entry.delete(key)
|
48
|
+
@time_entry.delete(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(retrieved_time_entry).to eq @time_entry
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'updates a time entry' do
|
55
|
+
time_entry_info = {
|
56
|
+
'start' => '2010-02-13T23:31:30+00:00',
|
57
|
+
'duration' => 42
|
58
|
+
}
|
59
|
+
|
60
|
+
expected = time_entry_info.clone
|
61
|
+
|
62
|
+
time_entry_updated = @toggl.update_time_entry(@time_entry['id'], time_entry_info)
|
63
|
+
expect(time_entry_updated).to include(expected)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'deletes a time entry' do
|
67
|
+
existing_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
68
|
+
expect(existing_time_entry.has_key?('server_deleted_at')).to eq false
|
69
|
+
|
70
|
+
deleted_time_entry = @toggl.delete_time_entry(@time_entry['id'])
|
71
|
+
expect(deleted_time_entry).to eq "[#{ @time_entry['id'] }]"
|
72
|
+
|
73
|
+
zombie_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
74
|
+
expect(zombie_time_entry.has_key?('server_deleted_at')).to eq true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context '+ UTC offset' do
|
79
|
+
# ISO8601 times with positive '+' UTC offsets must be properly encoded
|
80
|
+
|
81
|
+
before :each do
|
82
|
+
time_entry_info = {
|
83
|
+
'wid' => @workspace_id,
|
84
|
+
'start' => '2016-01-22T12:08:14+02:00',
|
85
|
+
'duration' => 77
|
86
|
+
}
|
87
|
+
|
88
|
+
@expected = time_entry_info.clone
|
89
|
+
|
90
|
+
@time_entry = @toggl.create_time_entry(time_entry_info)
|
91
|
+
end
|
92
|
+
|
93
|
+
after :each do
|
94
|
+
@toggl.delete_time_entry(@time_entry['id'])
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'creates a time entry' do
|
98
|
+
expect(@time_entry).to include(@expected)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'requires a workspace, project, or task to create' do
|
102
|
+
time_entry_info = {
|
103
|
+
'start' => '2016-01-22T12:08:14+02:00',
|
104
|
+
'duration' => 77
|
105
|
+
}
|
106
|
+
|
107
|
+
expect {
|
108
|
+
@toggl.create_time_entry(time_entry_info)
|
109
|
+
}.to raise_error(ArgumentError)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'gets a time entry' do
|
113
|
+
retrieved_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
114
|
+
|
115
|
+
['start', 'stop'].each do |key|
|
116
|
+
expect(retrieved_time_entry[key]).to eq_ts @time_entry[key]
|
117
|
+
retrieved_time_entry.delete(key)
|
118
|
+
@time_entry.delete(key)
|
119
|
+
end
|
120
|
+
|
121
|
+
expect(retrieved_time_entry).to eq @time_entry
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'updates a time entry' do
|
125
|
+
time_entry_info = {
|
126
|
+
'start' => '2010-02-13T23:31:30+07:00',
|
127
|
+
'duration' => 42
|
128
|
+
}
|
129
|
+
|
130
|
+
expected = time_entry_info.clone
|
131
|
+
|
132
|
+
time_entry_updated = @toggl.update_time_entry(@time_entry['id'], time_entry_info)
|
133
|
+
expect(time_entry_updated).to include(expected)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'deletes a time entry' do
|
137
|
+
existing_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
138
|
+
expect(existing_time_entry.has_key?('server_deleted_at')).to eq false
|
139
|
+
|
140
|
+
deleted_time_entry = @toggl.delete_time_entry(@time_entry['id'])
|
141
|
+
expect(deleted_time_entry).to eq "[#{ @time_entry['id'] }]"
|
142
|
+
|
143
|
+
zombie_time_entry = @toggl.get_time_entry(@time_entry['id'])
|
144
|
+
expect(zombie_time_entry.has_key?('server_deleted_at')).to eq true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'multiple time entries' do
|
149
|
+
before :all do
|
150
|
+
time_entry_info = {
|
151
|
+
'wid' => @workspace_id,
|
152
|
+
'duration' => 77
|
153
|
+
}
|
154
|
+
@now = DateTime.now
|
155
|
+
|
156
|
+
start = { 'start' => @toggl.iso8601(@now - 9) }
|
157
|
+
@time_entry_nine_days_ago = @toggl.create_time_entry(time_entry_info.merge(start))
|
158
|
+
@nine_days_ago_id = @time_entry_nine_days_ago['id']
|
159
|
+
|
160
|
+
start = { 'start' => @toggl.iso8601(@now - 7) }
|
161
|
+
@time_entry_last_week = @toggl.create_time_entry(time_entry_info.merge(start))
|
162
|
+
@last_week_id = @time_entry_last_week['id']
|
163
|
+
|
164
|
+
start = { 'start' => @toggl.iso8601(@now) }
|
165
|
+
@time_entry_now = @toggl.create_time_entry(time_entry_info.merge(start))
|
166
|
+
@now_id = @time_entry_now['id']
|
167
|
+
|
168
|
+
start = { 'start' => @toggl.iso8601(@now + 7) }
|
169
|
+
@time_entry_next_week = @toggl.create_time_entry(time_entry_info.merge(start))
|
170
|
+
@next_week_id = @time_entry_next_week['id']
|
171
|
+
end
|
172
|
+
|
173
|
+
after :all do
|
174
|
+
TogglV8SpecHelper.delete_all_time_entries(@toggl)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'gets time entries (reaching back 9 days up till now)' do
|
178
|
+
ids = @toggl.get_time_entries.map { |t| t['id']}
|
179
|
+
expect(ids).to eq [ @last_week_id, @now_id ]
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'gets time entries after start_date (up till now)' do
|
183
|
+
ids = @toggl.get_time_entries({:start_date => @now - 1}).map { |t| t['id']}
|
184
|
+
expect(ids).to eq [ @now_id ]
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'gets time entries between start_date and end_date' do
|
188
|
+
ids = @toggl.get_time_entries({:start_date => @now - 1, :end_date => @now + 1}).map { |t| t['id']}
|
189
|
+
expect(ids).to eq [ @now_id ]
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'gets time entries in the future' do
|
193
|
+
ids = @toggl.get_time_entries({:start_date => @now - 1, :end_date => @now + 8}).map { |t| t['id']}
|
194
|
+
expect(ids).to eq [ @now_id, @next_week_id ]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'start and stop time entry' do
|
199
|
+
it 'starts and stops a time entry' do
|
200
|
+
time_entry_info = {
|
201
|
+
'wid' => @workspace_id,
|
202
|
+
'description' => 'time entry description'
|
203
|
+
}
|
204
|
+
|
205
|
+
# start time entry
|
206
|
+
running_time_entry = @toggl.start_time_entry(time_entry_info)
|
207
|
+
|
208
|
+
# get current time entry by '/current'
|
209
|
+
time_entry_current = @toggl.get_current_time_entry
|
210
|
+
# get current time entry by id
|
211
|
+
time_entry_by_id = @toggl.get_time_entry(running_time_entry['id'])
|
212
|
+
|
213
|
+
# compare two methods of getting current time entry
|
214
|
+
expect(time_entry_current).to eq time_entry_by_id
|
215
|
+
|
216
|
+
# compare current time entry with running time entry
|
217
|
+
expect(time_entry_by_id['start']).to eq_ts running_time_entry['start']
|
218
|
+
time_entry_by_id.delete('start')
|
219
|
+
running_time_entry.delete('start')
|
220
|
+
|
221
|
+
expect(time_entry_by_id).to eq running_time_entry
|
222
|
+
expect(time_entry_by_id.has_key?('stop')).to eq false
|
223
|
+
|
224
|
+
# stop time entry
|
225
|
+
stopped_time_entry = @toggl.stop_time_entry(running_time_entry['id'])
|
226
|
+
expect(stopped_time_entry.has_key?('stop')).to eq true
|
227
|
+
|
228
|
+
@toggl.delete_time_entry(stopped_time_entry['id'])
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'returns nil if there is no current time entry' do
|
232
|
+
time_entry = @toggl.get_current_time_entry
|
233
|
+
expect(time_entry).to be nil
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'requires a workspace, project, or task to start' do
|
237
|
+
time_entry_info = {
|
238
|
+
'description' => 'time entry description'
|
239
|
+
}
|
240
|
+
|
241
|
+
expect {
|
242
|
+
@toggl.start_time_entry(time_entry_info)
|
243
|
+
}.to raise_error(ArgumentError)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'time entry tags' do
|
248
|
+
before :each do
|
249
|
+
time_entry_info = {
|
250
|
+
'wid' => @workspace_id,
|
251
|
+
'duration' => 7777
|
252
|
+
}
|
253
|
+
@now = DateTime.now
|
254
|
+
|
255
|
+
start = { 'start' => @toggl.iso8601(@now - 7) }
|
256
|
+
@time7 = @toggl.create_time_entry(time_entry_info.merge(start))
|
257
|
+
|
258
|
+
start = { 'start' => @toggl.iso8601(@now - 6) }
|
259
|
+
@time6 = @toggl.create_time_entry(time_entry_info.merge(start))
|
260
|
+
|
261
|
+
start = { 'start' => @toggl.iso8601(@now - 5) }
|
262
|
+
@time5 = @toggl.create_time_entry(time_entry_info.merge(start))
|
263
|
+
|
264
|
+
start = { 'start' => @toggl.iso8601(@now - 4) }
|
265
|
+
@time4 = @toggl.create_time_entry(time_entry_info.merge(start))
|
266
|
+
|
267
|
+
@time_entry_ids = [ @time7['id'], @time6['id'], @time5['id'], @time4['id']]
|
268
|
+
end
|
269
|
+
|
270
|
+
after :each do
|
271
|
+
TogglV8SpecHelper.delete_all_time_entries(@toggl)
|
272
|
+
TogglV8SpecHelper.delete_all_tags(@toggl)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'adds and removes one tag' do
|
276
|
+
# Add one tag
|
277
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
278
|
+
{'tags' =>['money'], 'tag_action' => 'add'})
|
279
|
+
|
280
|
+
time_entries = @toggl.get_time_entries
|
281
|
+
tags = time_entries.map { |t| t['tags'] }
|
282
|
+
expect(tags).to eq [
|
283
|
+
['money'],
|
284
|
+
['money'],
|
285
|
+
['money'],
|
286
|
+
['money']
|
287
|
+
]
|
288
|
+
|
289
|
+
# Remove one tag
|
290
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
291
|
+
{'tags' =>['money'], 'tag_action' => 'remove'})
|
292
|
+
|
293
|
+
time_entries = @toggl.get_time_entries
|
294
|
+
tags = time_entries.map { |t| t['tags'] }.compact
|
295
|
+
expect(tags).to eq []
|
296
|
+
end
|
297
|
+
|
298
|
+
it '"removes" a non-existent tag' do
|
299
|
+
# Not tags to start
|
300
|
+
time_entries = @toggl.get_time_entries
|
301
|
+
tags = time_entries.map { |t| t['tags'] }.compact
|
302
|
+
expect(tags).to eq []
|
303
|
+
|
304
|
+
# "Remove" a tag
|
305
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
306
|
+
{'tags' =>['void'], 'tag_action' => 'remove'})
|
307
|
+
|
308
|
+
# No tags to finish
|
309
|
+
time_entries = @toggl.get_time_entries
|
310
|
+
tags = time_entries.map { |t| t['tags'] }.compact
|
311
|
+
expect(tags).to eq []
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'adds and removes multiple tags' do
|
315
|
+
# Add multiple tags
|
316
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
317
|
+
{'tags' =>['billed', 'productive'], 'tag_action' => 'add'})
|
318
|
+
|
319
|
+
time_entries = @toggl.get_time_entries
|
320
|
+
tags = time_entries.map { |t| t['tags'] }
|
321
|
+
expect(tags).to eq [
|
322
|
+
['billed', 'productive'],
|
323
|
+
['billed', 'productive'],
|
324
|
+
['billed', 'productive'],
|
325
|
+
['billed', 'productive']
|
326
|
+
]
|
327
|
+
|
328
|
+
# Remove multiple tags
|
329
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
330
|
+
{'tags' =>['billed','productive'], 'tag_action' => 'remove'})
|
331
|
+
|
332
|
+
time_entries = @toggl.get_time_entries
|
333
|
+
tags = time_entries.map { |t| t['tags'] }.compact
|
334
|
+
expect(tags).to eq []
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'manages multiple tags' do
|
338
|
+
# Add some tags
|
339
|
+
@toggl.update_time_entries_tags_fixed(@time_entry_ids,
|
340
|
+
{'tags' =>['billed', 'productive'], 'tag_action' => 'add'})
|
341
|
+
|
342
|
+
# Remove some tags
|
343
|
+
@toggl.update_time_entries_tags_fixed([ @time6['id'], @time4['id'] ],
|
344
|
+
{'tags' =>['billed'], 'tag_action' => 'remove'})
|
345
|
+
|
346
|
+
# Add some tags
|
347
|
+
@toggl.update_time_entries_tags_fixed([ @time7['id'] ],
|
348
|
+
{'tags' =>['best'], 'tag_action' => 'add'})
|
349
|
+
|
350
|
+
time7 = @toggl.get_time_entry(@time7['id'])
|
351
|
+
time6 = @toggl.get_time_entry(@time6['id'])
|
352
|
+
time5 = @toggl.get_time_entry(@time5['id'])
|
353
|
+
time4 = @toggl.get_time_entry(@time4['id'])
|
354
|
+
|
355
|
+
tags = [ time7['tags'], time6['tags'], time5['tags'], time4['tags'] ]
|
356
|
+
expect(tags).to eq [
|
357
|
+
['best', 'billed', 'productive'],
|
358
|
+
[ 'productive'],
|
359
|
+
[ 'billed', 'productive'],
|
360
|
+
[ 'productive']
|
361
|
+
]
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context 'iso8601' do
|
366
|
+
before :all do
|
367
|
+
@ts = DateTime.new(2008,6,21, 13,30,2, "+09:00")
|
368
|
+
@expected = '2008-06-21T13:30:02+09:00'
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'formats a DateTime' do
|
372
|
+
expect(@toggl.iso8601(@ts)).to eq @expected
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'formats a Date' do
|
376
|
+
ts = @ts.to_date
|
377
|
+
expect(@toggl.iso8601(@ts)).to eq @expected
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'formats a Time' do
|
381
|
+
ts = @ts.to_time
|
382
|
+
expect(@toggl.iso8601(@ts)).to eq @expected
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'cannot format a FixNum' do
|
386
|
+
expect{ @toggl.iso8601(1234567890) }.to raise_error(ArgumentError)
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'cannot format a malformed timestamp' do
|
390
|
+
expect{ @toggl.iso8601('X') }.to raise_error(ArgumentError)
|
391
|
+
end
|
392
|
+
|
393
|
+
context 'String' do
|
394
|
+
it 'converts +00:00 to Zulu' do
|
395
|
+
ts = '2015-08-21T09:21:02+00:00'
|
396
|
+
expected = '2015-08-21T09:21:02Z'
|
397
|
+
|
398
|
+
expect(@toggl.iso8601(ts)).to eq expected
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'converts -00:00 to Z' do
|
402
|
+
ts = '2015-08-21T09:21:02-00:00'
|
403
|
+
expected = '2015-08-21T09:21:02Z'
|
404
|
+
|
405
|
+
expect(@toggl.iso8601(ts)).to eq expected
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'maintains an offset' do
|
409
|
+
expect(@toggl.iso8601('2015-08-21T04:21:02-05:00')).to eq '2015-08-21T04:21:02-05:00'
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
RSpec::Matchers.define :eq_ts do |expected|
|
415
|
+
# Matching actual time is necessary due to differing formats.
|
416
|
+
# Example:
|
417
|
+
# 1) POST time_entries/start returns 2015-08-21T07:28:20Z
|
418
|
+
# when GET time_entries/{time_entry_id} returns 2015-08-21T07:28:20+00:00
|
419
|
+
# 2) 2015-08-21T03:20:30-05:00 and 2015-08-21T08:20:30+00:00 refer to
|
420
|
+
# the same moment in time, but one is in local time and the other in UTC
|
421
|
+
match do |actual|
|
422
|
+
DateTime.parse(actual) == DateTime.parse(expected)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe 'Users' do
|
2
|
+
before :all do
|
3
|
+
@toggl = TogglV8::API.new(Testing::API_TOKEN)
|
4
|
+
@user = @toggl.me(all=true)
|
5
|
+
@workspaces = @toggl.workspaces
|
6
|
+
@workspace_id = @workspaces.first['id']
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'returns /me' do
|
10
|
+
expect(@user).to_not be_nil
|
11
|
+
expect(@user['id']).to eq Testing::USER_ID
|
12
|
+
expect(@user['fullname']).to eq Testing::USERNAME
|
13
|
+
# expect(@user['image_url']).to eq 'https://assets.toggl.com/avatars/a5d106126b6bed8df283e708af0828ee.png'
|
14
|
+
# expect(@user['timezone']).to eq 'Etc/UTC'
|
15
|
+
# expect(@user['workspaces'].length).to eq 1
|
16
|
+
# expect(@user['workspaces'].first['name']).to eq "togglv8's workspace"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns my_clients' do
|
20
|
+
my_clients = @toggl.my_clients(@user)
|
21
|
+
expect(my_clients).to be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns my_projects' do
|
25
|
+
my_projects = @toggl.my_projects(@user)
|
26
|
+
expect(my_projects).to be_empty
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns my_projects and my_deleted_projects' do
|
30
|
+
# Create project
|
31
|
+
project = @toggl.create_project({ 'name' => 'my project', 'wid' => @workspace_id })
|
32
|
+
|
33
|
+
my_project_ids = @toggl.my_projects.map { |p| p['id'] }
|
34
|
+
my_deleted_project_ids = @toggl.my_deleted_projects.map { |p| p['id'] }
|
35
|
+
|
36
|
+
expect(my_project_ids).to eq [ project['id'] ]
|
37
|
+
expect(my_deleted_project_ids).not_to include(project['id'])
|
38
|
+
|
39
|
+
# Delete project
|
40
|
+
@toggl.delete_project(project['id'])
|
41
|
+
|
42
|
+
my_project_ids = @toggl.my_projects.map { |p| p['id'] }
|
43
|
+
my_deleted_project_ids = @toggl.my_deleted_projects.map { |p| p['id'] }
|
44
|
+
|
45
|
+
expect(my_project_ids).to eq []
|
46
|
+
expect(my_deleted_project_ids).to include(project['id'])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns my_tags' do
|
50
|
+
my_tags = @toggl.my_tags(@user)
|
51
|
+
expect(my_tags).to be_empty
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns my_tasks' do
|
55
|
+
my_tasks = @toggl.my_tasks(@user)
|
56
|
+
expect(my_tasks).to be_empty
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns my_time_entries' do
|
60
|
+
my_time_entries = @toggl.my_time_entries(@user)
|
61
|
+
expect(my_time_entries).to be_empty
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns my_workspaces' do
|
65
|
+
my_workspaces = @toggl.my_workspaces(@user)
|
66
|
+
expect(my_workspaces.length).to eq 1
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'new user' do
|
70
|
+
it 'creates a new user' do
|
71
|
+
now = Time.now.to_i
|
72
|
+
user_info = {
|
73
|
+
'email' => "test-#{now}+1@mailinator.com",
|
74
|
+
'timezone' => 'Etc/UTC'
|
75
|
+
}
|
76
|
+
user_password = { 'password' => "password-#{now}+1" }
|
77
|
+
|
78
|
+
new_user = @toggl.create_user(user_info.merge(user_password))
|
79
|
+
expect(new_user).to include(user_info)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
describe 'Workspaces' do
|
2
|
+
before :all do
|
3
|
+
@toggl = TogglV8::API.new(Testing::API_TOKEN)
|
4
|
+
@user = @toggl.me(all=true)
|
5
|
+
@workspaces = @toggl.workspaces
|
6
|
+
@workspace_id = @workspaces.first['id']
|
7
|
+
@project = @toggl.create_project({ 'name' => 'project with a task', 'wid' => @workspace_id })
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
@toggl.delete_project(@project['id'])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'shows users' do
|
15
|
+
users = @toggl.users(@workspace_id)
|
16
|
+
expect(users.length).to eq 2
|
17
|
+
|
18
|
+
expect(users.first['id']).to eq Testing::OTHER_USER_ID
|
19
|
+
expect(users.first['email']).to eq Testing::OTHER_EMAIL
|
20
|
+
expect(users.first['fullname']).to eq Testing::OTHER_USERNAME
|
21
|
+
|
22
|
+
expect(users.last['id']).to eq Testing::USER_ID
|
23
|
+
expect(users.last['email']).to eq Testing::EMAIL
|
24
|
+
expect(users.last['fullname']).to eq Testing::USERNAME
|
25
|
+
expect(users.last['default_wid']).to eq @workspace_id
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'tasks', :pro_account do
|
29
|
+
before :each do
|
30
|
+
@task = @toggl.create_task('name' => 'workspace task', 'pid' => @project['id'])
|
31
|
+
end
|
32
|
+
|
33
|
+
after :each do
|
34
|
+
@toggl.delete_task(@task['id'])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'shows tasks' do
|
38
|
+
tasks = @toggl.tasks(@workspace_id)
|
39
|
+
expect(tasks.length).to eq 1
|
40
|
+
expect(tasks.first['name']).to eq 'workspace task'
|
41
|
+
expect(tasks.first['pid']).to eq @project['id']
|
42
|
+
expect(tasks.first['wid']).to eq @workspace_id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
describe 'TogglV8' do
|
4
|
+
it 'initializes with api_token' do
|
5
|
+
toggl = TogglV8::API.new(Testing::API_TOKEN)
|
6
|
+
me = toggl.me
|
7
|
+
expect(me).to_not be nil
|
8
|
+
expect(me['api_token']).to eq Testing::API_TOKEN
|
9
|
+
expect(me['email']).to eq Testing::EMAIL
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'initializes with username and password' do
|
13
|
+
toggl = TogglV8::API.new(Testing::EMAIL, Testing::PASSWORD)
|
14
|
+
me = toggl.me
|
15
|
+
expect(me).to_not be nil
|
16
|
+
expect(me['api_token']).to eq Testing::API_TOKEN
|
17
|
+
expect(me['email']).to eq Testing::EMAIL
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not initialize with bogus api_token' do
|
21
|
+
toggl = TogglV8::API.new('4880nqor1orr9n241sn08070q33oq49s')
|
22
|
+
expect { toggl.me }.to raise_error(RuntimeError, "HTTP Status: 403")
|
23
|
+
end
|
24
|
+
|
25
|
+
context '.toggl file' do
|
26
|
+
before :each do
|
27
|
+
@tmp_home = mktemp_dir
|
28
|
+
@original_home = Dir.home
|
29
|
+
ENV['HOME'] = @tmp_home
|
30
|
+
end
|
31
|
+
|
32
|
+
after :each do
|
33
|
+
FileUtils.rm_rf(@tmp_home)
|
34
|
+
ENV['HOME'] = @original_home
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'initializes with .toggl file' do
|
38
|
+
toggl_file = File.join(@tmp_home, '.toggl')
|
39
|
+
File.open(toggl_file, 'w') { |file| file.write(Testing::API_TOKEN) }
|
40
|
+
|
41
|
+
toggl = TogglV8::API.new
|
42
|
+
me = toggl.me
|
43
|
+
expect(me).to_not be nil
|
44
|
+
expect(me['api_token']).to eq Testing::API_TOKEN
|
45
|
+
expect(me['email']).to eq Testing::EMAIL
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'initializes with .toggl file ending with a newline' do
|
49
|
+
toggl_file = File.join(@tmp_home, '.toggl')
|
50
|
+
File.open(toggl_file, 'w') { |file| file.write(Testing::API_TOKEN + "\n") }
|
51
|
+
|
52
|
+
toggl = TogglV8::API.new
|
53
|
+
me = toggl.me
|
54
|
+
expect(me).to_not be nil
|
55
|
+
expect(me['api_token']).to eq Testing::API_TOKEN
|
56
|
+
expect(me['email']).to eq Testing::EMAIL
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises error if .toggl file is missing' do
|
60
|
+
expect{ toggl = TogglV8::API.new }.to raise_error(RuntimeError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'handles errors' do
|
65
|
+
before :all do
|
66
|
+
@toggl = TogglV8::API.new(Testing::API_TOKEN)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'surfaces an HTTP Status Code in case of error' do
|
70
|
+
expect(@toggl.conn).to receive(:get).once.and_return(
|
71
|
+
MockResponse.new(400, {}, 'body'))
|
72
|
+
expect { @toggl.me }.to raise_error(RuntimeError, "HTTP Status: 400")
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'retries a request up to 3 times if a 429 is received' do
|
76
|
+
expect(@toggl.conn).to receive(:get).exactly(3).times.and_return(
|
77
|
+
MockResponse.new(429, {}, 'body'))
|
78
|
+
expect { @toggl.me }.to raise_error(RuntimeError, "HTTP Status: 429")
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'retries a request after 429' do
|
82
|
+
expect(@toggl.conn).to receive(:get).twice.and_return(
|
83
|
+
MockResponse.new(429, {}, 'body'),
|
84
|
+
MockResponse.new(200, {}, nil))
|
85
|
+
expect(@toggl.me).to eq({}) # response is {} in this case because body is nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|