togglv8-tfl 0.0.1

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.
@@ -0,0 +1,31 @@
1
+ describe 'Dashboard' do
2
+ before :all do
3
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
4
+ @workspaces = @toggl.workspaces
5
+ @workspace_id = @workspaces.first['id']
6
+ end
7
+
8
+ it 'gets nil dashboard data' do
9
+ dashboard = @toggl.dashboard(@workspace_id)
10
+ expect(dashboard).to eq Hash['most_active_user' => nil, 'activity' => nil]
11
+ end
12
+
13
+ context 'gets dashboard time entries' do
14
+ before :all do
15
+ @new_time_entry = @toggl.start_time_entry({ 'wid' => @workspace_id, 'description' => 'new time entry +1' })
16
+ end
17
+
18
+ after :all do
19
+ @toggl.delete_time_entry(@new_time_entry['id'])
20
+ end
21
+
22
+ it 'gets dashboard data' do
23
+ dashboard = @toggl.dashboard(@workspace_id)
24
+ expect(dashboard['most_active_user']).to be nil
25
+ expect(dashboard['activity']).to_not be nil
26
+ expect(dashboard['activity'].first['user_id']).to eq @toggl.me['id']
27
+ expect(dashboard['activity'].first['project_id']).to be nil
28
+ expect(dashboard['activity'].first['description']).to eq 'new time entry +1'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,111 @@
1
+ describe 'Projects' do
2
+ before :all do
3
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
4
+ @workspaces = @toggl.workspaces
5
+ @workspace_id = @workspaces.first['id']
6
+ end
7
+
8
+ it 'gets {} if there are no workspace projects' do
9
+ projects = @toggl.projects(@workspace_id)
10
+ expect(projects).to be_empty
11
+ end
12
+
13
+ context 'new project' do
14
+ before :all do
15
+ @project = @toggl.create_project({ 'name' => 'new project +1', 'wid' => @workspace_id })
16
+ project_ids = @toggl.my_projects.map { |p| p['id'] }
17
+ expect(project_ids).to eq [ @project['id'] ]
18
+ end
19
+
20
+ after :all do
21
+ TogglV8SpecHelper.delete_all_projects(@toggl)
22
+ projects = @toggl.my_projects
23
+ expect(projects).to be_empty
24
+ end
25
+
26
+ it 'creates a project' do
27
+ expect(@project).to_not be nil
28
+ expect(@project['name']).to eq 'new project +1'
29
+ expect(@project['billable']).to eq false
30
+ expect(@project['is_private']).to eq true
31
+ expect(@project['active']).to eq true
32
+ expect(@project['template']).to eq false
33
+ expect(@project['auto_estimates']).to eq false
34
+ expect(@project['wid']).to eq @workspace_id
35
+ end
36
+
37
+ it 'gets project data' do
38
+ project = @toggl.get_project(@project['id'])
39
+ expect(project).to_not be nil
40
+ expect(project['wid']).to eq @project['wid']
41
+ expect(project['name']).to eq @project['name']
42
+ expect(project['billable']).to eq @project['billable']
43
+ expect(project['is_private']).to eq @project['is_private']
44
+ expect(project['active']).to eq @project['active']
45
+ expect(project['template']).to eq @project['template']
46
+ expect(project['auto_estimates']).to eq @project['auto_estimates']
47
+ expect(project['at']).to_not be nil
48
+ end
49
+
50
+ it 'gets project users' do
51
+ users = @toggl.get_project_users(@project['id'])
52
+ expect(users.length).to eq 1
53
+ expect(users.first['uid']).to eq Testing::USER_ID
54
+ expect(users.first['manager']).to eq true
55
+ end
56
+ end
57
+
58
+ context 'updated project' do
59
+ before :each do
60
+ @project = @toggl.create_project({ 'name' => 'project to update', 'wid' => @workspace_id })
61
+ end
62
+
63
+ after :each do
64
+ @toggl.delete_project(@project['id'])
65
+ end
66
+
67
+ it 'updates project data' do
68
+ new_values = {
69
+ 'name' => 'PROJECT-NEW',
70
+ 'is_private' => false,
71
+ 'active' => false,
72
+ 'auto_estimates' => true,
73
+ }
74
+ project = @toggl.update_project(@project['id'], new_values)
75
+ expect(project).to include(new_values)
76
+ end
77
+
78
+ it 'updates Pro project data', :pro_account do
79
+ new_values = {
80
+ 'template' => true,
81
+ 'billable' => true,
82
+ }
83
+ project = @toggl.update_project(@project['id'], new_values)
84
+ expect(project).to include(new_values)
85
+ end
86
+ end
87
+
88
+ context 'multiple projects' do
89
+ after :all do
90
+ TogglV8SpecHelper.delete_all_projects(@toggl)
91
+ end
92
+
93
+ it 'deletes multiple projects' do
94
+ # start with no projects
95
+ expect(@toggl.projects(@workspace_id)).to be_empty
96
+
97
+ p1 = @toggl.create_project({ 'name' => 'p1', 'wid' => @workspace_id })
98
+ p2 = @toggl.create_project({ 'name' => 'p2', 'wid' => @workspace_id })
99
+ p3 = @toggl.create_project({ 'name' => 'p3', 'wid' => @workspace_id })
100
+
101
+ # see 3 new projects
102
+ expect(@toggl.projects(@workspace_id).length).to eq 3
103
+
104
+ p_ids = [p1, p2, p3].map { |p| p['id'] }
105
+ @toggl.delete_projects(p_ids)
106
+
107
+ # end with no projects
108
+ expect(@toggl.projects(@workspace_id)).to be_empty
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,54 @@
1
+ describe 'Tags' do
2
+ before :all do
3
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
4
+ @workspaces = @toggl.workspaces
5
+ @workspace_id = @workspaces.first['id']
6
+ end
7
+
8
+ context 'new tag' do
9
+ before :all do
10
+ @tag = @toggl.create_tag({ 'name' => 'new tag +1', 'wid' => @workspace_id })
11
+ tag_ids = @toggl.my_tags.map { |t| t['id'] }
12
+ expect(tag_ids).to eq [ @tag['id'] ]
13
+ end
14
+
15
+ after :all do
16
+ TogglV8SpecHelper.delete_all_tags(@toggl)
17
+ tags = @toggl.my_tags
18
+ expect(tags).to be_empty
19
+ end
20
+
21
+ it 'creates a tag' do
22
+ expect(@tag).to_not be nil
23
+ expect(@tag['name']).to eq 'new tag +1'
24
+ expect(@tag['notes']).to eq nil
25
+ expect(@tag['wid']).to eq @workspace_id
26
+ end
27
+
28
+ it 'returns tag associated with workspace_id' do
29
+ tags = @toggl.tags(@workspace_id)
30
+ expect(tags).not_to be_empty
31
+ expect(tags.first['name']).to eq 'new tag +1'
32
+ expect(tags.first['wid']).to eq @workspace_id
33
+ end
34
+ end
35
+
36
+ context 'updated tag' do
37
+ before :each do
38
+ @tag = @toggl.create_tag({ 'name' => 'tag to update', 'wid' => @workspace_id })
39
+ end
40
+
41
+ after :each do
42
+ @toggl.delete_tag(@tag['id'])
43
+ end
44
+
45
+ it 'updates tag data' do
46
+ new_values = {
47
+ 'name' => 'TAG-NEW',
48
+ }
49
+
50
+ tag = @toggl.update_tag(@tag['id'], new_values)
51
+ expect(tag).to include(new_values)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,100 @@
1
+ describe 'Tasks', :pro_account do
2
+ before :all do
3
+ @toggl = TogglV8::API.new(Testing::API_TOKEN)
4
+ @workspaces = @toggl.workspaces
5
+ @workspace_id = @workspaces.first['id']
6
+ @project = @toggl.create_project({ 'name' => 'project with a task', 'wid' => @workspace_id })
7
+ end
8
+
9
+ after :all do
10
+ @toggl.delete_project(@project['id'])
11
+ end
12
+
13
+ context 'new task' do
14
+ before :all do
15
+ @task = @toggl.create_task({ 'name' => 'new task +1', 'pid' => @project['id'] })
16
+ @task_ids = @toggl.get_project_tasks(@project['id']).map { |t| t['id'] }
17
+ expect(@task_ids).to eq [ @task['id'] ]
18
+ end
19
+
20
+ after :all do
21
+ @toggl.delete_tasks(@task_ids)
22
+ tasks = @toggl.get_project_tasks(@project['id'])
23
+ expect(tasks).to be_empty
24
+ end
25
+
26
+ it 'creates a task' do
27
+ expect(@task).to_not be nil
28
+ expect(@task['name']).to eq 'new task +1'
29
+ expect(@task['pid']).to eq @project['id']
30
+ expect(@task['wid']).to eq @workspace_id
31
+ expect(@task['active']).to eq true
32
+ end
33
+
34
+ it 'gets a task' do
35
+ task = @toggl.get_task(@task['id'])
36
+ expect(task).to include('at') # 'at' is last updated timestamp
37
+ task.delete('at') # 'at' is not included in POST response
38
+ expect(task).to eq @task # compare POST and GET responses
39
+ end
40
+ end
41
+
42
+ context 'updated task' do
43
+ before :each do
44
+ @task = @toggl.create_task({ 'name' => 'task to update', 'pid' => @project['id'] })
45
+ end
46
+
47
+ after :each do
48
+ @toggl.delete_task(@task['id'])
49
+ end
50
+
51
+ it 'updates task data' do
52
+ new_values = {
53
+ 'name' => 'task-NEW',
54
+ }
55
+
56
+ task = @toggl.update_task(@task['id'], new_values)
57
+ expect(task).to include(new_values)
58
+ end
59
+ end
60
+
61
+ context 'multiple tasks' do
62
+ before :each do
63
+ timestamp = Time.now.strftime("%H%M%S.%9N")
64
+ @task1 = @toggl.create_task({ 'name' => "task1-#{timestamp}", 'pid' => @project['id'] })
65
+ @task2 = @toggl.create_task({ 'name' => "task2-#{timestamp}", 'pid' => @project['id'] })
66
+ @task3 = @toggl.create_task({ 'name' => "task3-#{timestamp}", 'pid' => @project['id'] })
67
+ @task_ids = [ @task1['id'], @task2['id'], @task3['id'] ]
68
+ end
69
+
70
+ after :all do
71
+ @toggl.delete_tasks(@task_ids)
72
+ end
73
+
74
+ it 'updates multiple tasks' do
75
+ # start with 3 active tasks
76
+ tasks = @toggl.get_project_tasks(@project['id'])
77
+ active_flags = tasks.map { |t| t['active'] }
78
+ expect(active_flags).to match_array([true, true, true])
79
+
80
+ t_ids = [@task1, @task2, @task3].map { |t| t['id'] }
81
+ params = { 'active' => false }
82
+ @toggl.update_tasks(t_ids, params)
83
+
84
+ # end with no active tasks
85
+ tasks = @toggl.get_project_tasks(@project['id'])
86
+ expect(tasks).to be_empty
87
+ end
88
+
89
+ it 'deletes multiple tasks' do
90
+ # start with 3 new tasks
91
+ expect(@toggl.get_project_tasks(@project['id']).length).to eq 3
92
+
93
+ t_ids = [@task1, @task2, @task3].map { |t| t['id'] }
94
+ @toggl.delete_tasks(t_ids)
95
+
96
+ # end with no active tasks
97
+ expect(@toggl.get_project_tasks(@project['id'])).to be_empty
98
+ end
99
+ end
100
+ end
@@ -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(@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(@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(@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(@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(@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(@time_entry_ids,
340
+ {'tags' =>['billed', 'productive'], 'tag_action' => 'add'})
341
+
342
+ # Remove some tags
343
+ @toggl.update_time_entries_tags([ @time6['id'], @time4['id'] ],
344
+ {'tags' =>['billed'], 'tag_action' => 'remove'})
345
+
346
+ # Add some tags
347
+ @toggl.update_time_entries_tags([ @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