togglv8 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Projects
7
+ #
8
+ # See Toggl {Projects}[https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md]
9
+ #
10
+ # name : The name of the project
11
+ # (string, *required*, unique for client and workspace)
12
+ # wid : workspace ID, where the project will be saved
13
+ # (integer, *required*)
14
+ # cid : client ID
15
+ # (integer, not required)
16
+ # active : whether the project is archived or not
17
+ # (boolean, by default true)
18
+ # is_private : whether project is accessible for only project users or for all workspace users
19
+ # (boolean, default true)
20
+ # template : whether the project can be used as a template
21
+ # (boolean, not required)
22
+ # template_id : id of the template project used on current project's creation
23
+ # billable : whether the project is billable or not
24
+ # (boolean, default true, available only for pro workspaces)
25
+ # auto_estimates : whether the estimated hours is calculated based on task estimations or is fixed manually
26
+ # (boolean, default false, not required, premium functionality)
27
+ # estimated_hours : if auto_estimates is true then the sum of task estimations is returned, otherwise user inserted hours
28
+ # (integer, not required, premium functionality)
29
+ # at : timestamp that is sent in the response for PUT, indicates the time task was last updated
30
+ # color : id of the color selected for the project
31
+ # rate : hourly rate of the project
32
+ # (float, not required, premium functionality)
33
+ # created_at : timestamp indicating when the project was created (UTC time), read-only
34
+ # ---------
35
+
36
+ ##
37
+ # :category: Projects
38
+ #
39
+ # Public: Create a new project
40
+ #
41
+ # params - The Hash used to create the project (default: {})
42
+ # :name - The name of the project (string, required, unique for client and workspace)
43
+ # :wid - workspace ID, where the project will be saved (integer, required)
44
+ # :cid - client ID (integer, not required)
45
+ # :active - whether the project is archived or not (boolean, by default true)
46
+ # :is_private - whether project is accessible for only project users or for all workspace users (boolean, default true)
47
+ # :template - whether the project can be used as a template (boolean, not required)
48
+ # :template_id - id of the template project used on current project's creation
49
+ # :billable - whether the project is billable or not (boolean, default true, available only for pro workspaces)
50
+ # :auto_estimates - whether the estimated hours is calculated based on task estimations or is fixed manually (boolean, default false, not required, premium functionality)
51
+ # :estimated_hours - if auto_estimates is true then the sum of task estimations is returned, otherwise user inserted hours (integer, not required, premium functionality)
52
+ # :at - timestamp that is sent in the response for PUT, indicates the time task was last updated
53
+ # :color - id of the color selected for the project
54
+ # :rate - hourly rate of the project (float, not required, premium functionality)
55
+ # :created_at - timestamp indicating when the project was created (UTC time), read-only
56
+ #
57
+ # Examples
58
+ #
59
+ # toggl.create_project({ name: 'My project', wid: 1060392 })
60
+ # => {"id"=>10918774,
61
+ # "wid"=>1060392,
62
+ # "name"=>"project5",
63
+ # "billable"=>false,
64
+ # "is_private"=>true,
65
+ # "active"=>true,
66
+ # "template"=>false,
67
+ # "at"=>"2015-08-18T10:03:51+00:00",
68
+ # "color"=>"5",
69
+ # "auto_estimates"=>false}
70
+ #
71
+ # Returns a +Hash+ representing the newly created Project.
72
+ #
73
+ # See Toggl {Create Project}[https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#create-project]
74
+ def create_project(params)
75
+ requireParams(params, ['name', 'wid'])
76
+ post "projects", {project: params}
77
+ end
78
+
79
+ # [Get project data](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-data)
80
+ def get_project(project_id)
81
+ get "projects/#{project_id}"
82
+ end
83
+
84
+ # [Update project data](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#update-project-data)
85
+ def update_project(project_id, params)
86
+ put "projects/#{project_id}", { 'project' => params }
87
+ end
88
+
89
+ # [Delete a project](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#delete-a-project)
90
+ def delete_project(project_id)
91
+ delete "projects/#{project_id}"
92
+ end
93
+
94
+ # [Get project users](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-users)
95
+ def get_project_users(project_id)
96
+ get "projects/#{project_id}/project_users"
97
+ end
98
+
99
+ # [Get project tasks](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-tasks)
100
+ def get_project_tasks(project_id)
101
+ get "projects/#{project_id}/project_tasks"
102
+ end
103
+
104
+ # [Get workspace projects](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-workspace-projects)
105
+
106
+ # [Delete multiple projects](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#delete-multiple-projects)
107
+ def delete_projects(project_ids)
108
+ delete "projects/#{project_ids.join(',')}"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,25 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Tags
7
+ #
8
+ # name : The name of the tag (string, required, unique in workspace)
9
+ # wid : workspace ID, where the tag will be used (integer, required)
10
+
11
+ def create_tag(params)
12
+ requireParams(params, ['name', 'wid'])
13
+ post "tags", {tag: params}
14
+ end
15
+
16
+ # ex: update_tag(12345, {name: "same tame game"})
17
+ def update_tag(tag_id, params)
18
+ put "tags/#{tag_id}", { 'tag' => params }
19
+ end
20
+
21
+ def delete_tag(tag_id)
22
+ delete "tags/#{tag_id}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,53 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Tasks
7
+ #
8
+ # NOTE: Tasks are available only for pro workspaces.
9
+ #
10
+ # name : The name of the task (string, required, unique in project)
11
+ # pid : project ID for the task (integer, required)
12
+ # wid : workspace ID, where the task will be saved
13
+ # (integer, project's workspace id is used when not supplied)
14
+ # uid : user ID, to whom the task is assigned to (integer, not required)
15
+ # estimated_seconds : estimated duration of task in seconds (integer, not required)
16
+ # active : whether the task is done or not (boolean, by default true)
17
+ # at : timestamp that is sent in the response for PUT, indicates the time task was last updated
18
+ # -- Additional fields --
19
+ # done_seconds : duration (in seconds) of all the time entries registered for this task
20
+ # uname : full name of the person to whom the task is assigned to
21
+
22
+ def create_task(params)
23
+ requireParams(params, [:name, :pid])
24
+ post "tasks", { 'task' => params }
25
+ end
26
+
27
+ def get_task(task_id)
28
+ get "tasks/#{task_id}"
29
+ end
30
+
31
+ # ex: update_task(1894675, {active: true, estimated_seconds: 4500, fields: "done_seconds,uname"})
32
+ def update_task(task_id, params)
33
+ put "tasks/#{task_id.join(',')}", { 'task' => params }
34
+ end
35
+
36
+ def delete_task(task_id)
37
+ delete "tasks/#{task_id.join(',')}"
38
+ end
39
+
40
+ # ------------ #
41
+ # Mass Actions #
42
+ # ------------ #
43
+
44
+ def update_tasks(task_ids, params)
45
+ put "tasks/#{task_ids.join(',')}", { 'task' => params }
46
+ end
47
+
48
+ def delete_tasks(task_ids)
49
+ delete "tasks/#{task_ids.join(',')}"
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,89 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Time Entries
7
+ #
8
+ # https://github.com/toggl/toggl_api_docs/blob/master/chapters/time_entries.md
9
+ #
10
+ # description : (string, strongly suggested to be used)
11
+ # wid : workspace ID (integer, required if pid or tid not supplied)
12
+ # pid : project ID (integer, not required)
13
+ # tid : task ID (integer, not required)
14
+ # billable : (boolean, not required, default false, available for pro workspaces)
15
+ # start : time entry start time (string, required, ISO 8601 date and time)
16
+ # stop : time entry stop time (string, not required, ISO 8601 date and time)
17
+ # duration : time entry duration in seconds. If the time entry is currently running,
18
+ # the duration attribute contains a negative value,
19
+ # denoting the start of the time entry in seconds since epoch (Jan 1 1970).
20
+ # The correct duration can be calculated as current_time + duration,
21
+ # where current_time is the current time in seconds since epoch. (integer, required)
22
+ # created_with : the name of your client app (string, required)
23
+ # tags : a list of tag names (array of strings, not required)
24
+ # duronly : should Toggl show the start and stop time of this time entry? (boolean, not required)
25
+ # at : timestamp that is sent in the response, indicates the time item was last updated
26
+
27
+ def create_time_entry(params)
28
+ params['created_with'] = 'TogglV8' unless params.has_key?('created_with')
29
+ requireParams(params, ['start', 'duration', 'created_with'])
30
+ if !params.has_key?('wid') and !params.has_key?('pid') and !params.has_key?('tid') then
31
+ raise ArgumentError, "one of params['wid'], params['pid'], params['tid'] is required"
32
+ end
33
+ post "time_entries", { 'time_entry' => params }
34
+ end
35
+
36
+ def start_time_entry(params)
37
+ params['created_with'] = 'TogglV8' unless params.has_key?('created_with')
38
+ if !params.has_key?('wid') and !params.has_key?('pid') and !params.has_key?('tid') then
39
+ raise ArgumentError, "one of params['wid'], params['pid'], params['tid'] is required"
40
+ end
41
+ post "time_entries/start", {time_entry: params}
42
+ end
43
+
44
+ def stop_time_entry(time_entry_id)
45
+ put "time_entries/#{time_entry_id}/stop", {}
46
+ end
47
+
48
+ def get_time_entry(time_entry_id)
49
+ get "time_entries/#{time_entry_id}"
50
+ end
51
+
52
+ def get_current_time_entry
53
+ get "time_entries/current"
54
+ end
55
+
56
+ def update_time_entry(time_entry_id, params)
57
+ put "time_entries/#{time_entry_id}", { 'time_entry' => params }
58
+ end
59
+
60
+ def delete_time_entry(time_entry_id)
61
+ delete "time_entries/#{time_entry_id}"
62
+ end
63
+
64
+ def iso8601(timestamp)
65
+ return nil if timestamp.nil?
66
+ if timestamp.is_a?(DateTime) or timestamp.is_a?(Date)
67
+ formatted_ts = timestamp.iso8601
68
+ elsif timestamp.is_a?(String)
69
+ formatted_ts = DateTime.parse(timestamp).iso8601
70
+ else
71
+ raise ArgumentError, "Can't convert #{timestamp.class} to ISO-8601 Date/Time"
72
+ end
73
+ return formatted_ts.sub('+00:00', 'Z')
74
+ end
75
+
76
+ def get_time_entries(start_timestamp=nil, end_timestamp=nil)
77
+ params = []
78
+ params.push("start_date=#{iso8601(start_timestamp)}") if !start_timestamp.nil?
79
+ params.push("end_date=#{iso8601(end_timestamp)}") if !end_timestamp.nil?
80
+ get "time_entries%s" % [params.empty? ? "" : "?#{params.join('&')}"]
81
+ end
82
+
83
+ # Example params: {"tags":["billed","productive"], "tag_action": "add"}
84
+ # tag_action can be 'add' or 'remove'
85
+ def update_time_entries_tags(time_entry_ids, params)
86
+ put "time_entries/#{time_entry_ids.join(',')}", { 'time_entry' => params }
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,60 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Users
7
+ #
8
+ # api_token : (string)
9
+ # default_wid : default workspace id (integer)
10
+ # email : (string)
11
+ # jquery_timeofday_format : (string)
12
+ # jquery_date_format : (string)
13
+ # timeofday_format : (string)
14
+ # date_format : (string)
15
+ # store_start_and_stop_time : whether start and stop time are saved on time entry (boolean)
16
+ # beginning_of_week : (integer, Sunday=0)
17
+ # language : user's language (string)
18
+ # image_url : url with the user's profile picture(string)
19
+ # sidebar_piechart : should a piechart be shown on the sidebar (boolean)
20
+ # at : timestamp of last changes
21
+ # new_blog_post : an object with toggl blog post title and link
22
+
23
+ def me(all=nil)
24
+ # TODO: Reconcile this with get_client_projects
25
+ # NOTE: response['since'] is discarded
26
+ get "me%s" % [all.nil? ? "" : "?with_related_data=#{all}"]
27
+ end
28
+
29
+ def my_clients(user=nil)
30
+ user = me(all=true) if user.nil?
31
+ user['clients'] || {}
32
+ end
33
+
34
+ def my_projects(user=nil)
35
+ user = me(all=true) if user.nil?
36
+ user['projects'] || {}
37
+ end
38
+
39
+ def my_tags(user=nil)
40
+ user = me(all=true) if user.nil?
41
+ user['tags'] || {}
42
+ end
43
+
44
+ def my_time_entries(user=nil)
45
+ user = me(all=true) if user.nil?
46
+ user['time_entries'] || {}
47
+ end
48
+
49
+ def my_workspaces(user=nil)
50
+ user = me(all=true) if user.nil?
51
+ user['workspaces'] || {}
52
+ end
53
+
54
+ def create_user(params)
55
+ params['created_with'] = 'TogglV8' unless params.has_key?('created_with')
56
+ requireParams(params, ['email', 'password', 'timezone', 'created_with'])
57
+ post "signups", { 'user' => params }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,4 @@
1
+ module TogglV8
2
+ # :section:
3
+ VERSION = "0.2.0"
4
+ end
@@ -0,0 +1,43 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Workspaces
7
+ #
8
+ # name : (string, required)
9
+ # premium : If it's a pro workspace or not.
10
+ # Shows if someone is paying for the workspace or not (boolean, not required)
11
+ # at : timestamp that is sent in the response, indicates the time item was last updated
12
+
13
+ def workspaces
14
+ get "workspaces"
15
+ end
16
+
17
+ def clients(workspace_id=nil)
18
+ if workspace_id.nil?
19
+ get "clients"
20
+ else
21
+ get "workspaces/#{workspace_id}/clients"
22
+ end
23
+ end
24
+
25
+ def projects(workspace_id, params={})
26
+ active = params.has_key?(:active) ? "?active=#{params[:active]}" : ""
27
+ get "workspaces/#{workspace_id}/projects#{active}"
28
+ end
29
+
30
+ def users(workspace_id)
31
+ get "workspaces/#{workspace_id}/users"
32
+ end
33
+
34
+ def tasks(workspace_id, params={})
35
+ active = params.has_key?(:active) ? "?active=#{params[:active]}" : ""
36
+ get "workspaces/#{workspace_id}/tasks#{active}"
37
+ end
38
+
39
+ def leave_workspace(workspace_id)
40
+ delete "workspaces/#{workspace_id}/leave"
41
+ end
42
+ end
43
+ end
data/lib/togglv8.rb ADDED
@@ -0,0 +1,137 @@
1
+ require 'faraday'
2
+ require 'oj'
3
+
4
+ require 'logger'
5
+ require 'awesome_print' # for debug output
6
+
7
+ require_relative 'togglv8/clients'
8
+ require_relative 'togglv8/dashboard'
9
+ require_relative 'togglv8/project_users'
10
+ require_relative 'togglv8/projects'
11
+ require_relative 'togglv8/tags'
12
+ require_relative 'togglv8/tasks'
13
+ require_relative 'togglv8/time_entries'
14
+ require_relative 'togglv8/users'
15
+ require_relative 'togglv8/version'
16
+ require_relative 'togglv8/workspaces'
17
+
18
+ # mode: :compat will convert symbols to strings
19
+ Oj.default_options = { mode: :compat }
20
+
21
+ module TogglV8
22
+ TOGGL_API_URL = 'https://www.toggl.com/api/'
23
+
24
+ class API
25
+ TOGGL_API_V8_URL = TOGGL_API_URL + 'v8/'
26
+ API_TOKEN = 'api_token'
27
+ TOGGL_FILE = '.toggl'
28
+
29
+ attr_reader :conn
30
+
31
+ def initialize(username=nil, password=API_TOKEN, opts={})
32
+ @logger = Logger.new(STDOUT)
33
+ @logger.level = Logger::WARN
34
+
35
+ if username.nil? && password == API_TOKEN
36
+ toggl_api_file = File.join(Dir.home, TOGGL_FILE)
37
+ if FileTest.exist?(toggl_api_file) then
38
+ username = IO.read(toggl_api_file)
39
+ else
40
+ raise "Expecting\n" +
41
+ " 1) api_token in file #{toggl_api_file}, or\n" +
42
+ " 2) parameter: (api_token), or\n" +
43
+ " 3) parameters: (username, password).\n" +
44
+ "\n\tSee https://github.com/toggl/toggl_api_docs/blob/master/chapters/authentication.md"
45
+ end
46
+ end
47
+
48
+ @conn = TogglV8::API.connection(username, password, opts)
49
+ end
50
+
51
+ #---------#
52
+ # Private #
53
+ #---------#
54
+
55
+ private
56
+
57
+ attr_writer :conn
58
+
59
+ def self.connection(username, password, opts={})
60
+ Faraday.new(url: TOGGL_API_V8_URL, ssl: {verify: true}) do |faraday|
61
+ faraday.request :url_encoded
62
+ faraday.response :logger, Logger.new('faraday.log') if opts[:log]
63
+ faraday.adapter Faraday.default_adapter
64
+ faraday.headers = { "Content-Type" => "application/json" }
65
+ faraday.basic_auth username, password
66
+ end
67
+ end
68
+
69
+
70
+ def requireParams(params, fields=[])
71
+ raise ArgumentError, 'params is not a Hash' unless params.is_a? Hash
72
+ return if fields.empty?
73
+ errors = []
74
+ for f in fields
75
+ errors.push("params[#{f}] is required") unless params.has_key?(f)
76
+ end
77
+ raise ArgumentError, errors.join(', ') if !errors.empty?
78
+ end
79
+
80
+
81
+ def get(resource)
82
+ @logger.debug("GET #{resource}")
83
+ full_resp = self.conn.get(resource)
84
+ # @logger.ap(full_resp.env, :debug)
85
+
86
+ raise 'Too many requests in a given amount of time.' if full_resp.status == 429
87
+ raise Oj.dump(full_resp.env) unless full_resp.success?
88
+ return {} if full_resp.body.nil? || full_resp.body == 'null'
89
+
90
+ resp = Oj.load(full_resp.body)
91
+
92
+ return resp['data'] if resp.respond_to?(:has_key?) && resp.has_key?('data')
93
+ resp
94
+ end
95
+
96
+ def post(resource, data='')
97
+ @logger.debug("POST #{resource} / #{data}")
98
+ full_resp = self.conn.post(resource, Oj.dump(data))
99
+ # @logger.ap(full_resp.env, :debug)
100
+
101
+ raise 'Too many requests in a given amount of time.' if full_resp.status == 429
102
+ raise Oj.dump(full_resp.env) unless full_resp.success?
103
+ return {} if full_resp.body.nil? || full_resp.body == 'null'
104
+
105
+ resp = Oj.load(full_resp.body)
106
+ resp['data']
107
+ end
108
+
109
+ def put(resource, data='')
110
+ @logger.debug("PUT #{resource} / #{data}")
111
+ full_resp = self.conn.put(resource, Oj.dump(data))
112
+ # @logger.ap(full_resp.env, :debug)
113
+
114
+ raise 'Too many requests in a given amount of time.' if full_resp.status == 429
115
+ raise Oj.dump(full_resp.env) unless full_resp.success?
116
+ return {} if full_resp.body.nil? || full_resp.body == 'null'
117
+
118
+ resp = Oj.load(full_resp.body)
119
+ resp['data']
120
+ end
121
+
122
+ def delete(resource)
123
+ @logger.debug("DELETE #{resource}")
124
+ full_resp = self.conn.delete(resource)
125
+ # @logger.ap(full_resp.env, :debug)
126
+
127
+ raise 'Too many requests in a given amount of time.' if full_resp.status == 429
128
+
129
+ raise Oj.dump(full_resp.env) unless full_resp.success?
130
+ return {} if full_resp.body.nil? || full_resp.body == 'null'
131
+
132
+ full_resp.body
133
+ end
134
+
135
+ end
136
+
137
+ end
@@ -0,0 +1,139 @@
1
+ describe 'Clients' 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 clients' do
9
+ client = @toggl.clients
10
+ expect(client).to be_empty
11
+ end
12
+
13
+ it 'gets {} if there are no workspace clients' do
14
+ client = @toggl.clients(@workspace_id)
15
+ expect(client).to be_empty
16
+ end
17
+
18
+ context 'new client' do
19
+ before :all do
20
+ @client = @toggl.create_client({ 'name' => 'new client', 'wid' => @workspace_id })
21
+ client_ids = @toggl.my_clients.map { |c| c['id'] }
22
+ expect(client_ids).to eq [ @client['id'] ]
23
+ end
24
+
25
+ after :all do
26
+ TogglV8SpecHelper.delete_all_clients(@toggl)
27
+ clients = @toggl.my_clients
28
+ expect(clients).to be_empty
29
+ end
30
+
31
+ it 'gets a client' do
32
+ client_ids = @toggl.clients.map { |c| c['id'] }
33
+ expect(client_ids).to eq [ @client['id'] ]
34
+ end
35
+
36
+ it 'gets a workspace client' do
37
+ client_ids = @toggl.clients(@workspace_id).map { |c| c['id'] }
38
+ expect(client_ids).to eq [ @client['id'] ]
39
+ end
40
+
41
+ context 'multiple clients' do
42
+ before :all do
43
+ @client2 = @toggl.create_client({ 'name' => 'new client 2', 'wid' => @workspace_id })
44
+ end
45
+
46
+ after :all do
47
+ @toggl.delete_client(@client2['id'])
48
+ end
49
+
50
+ it 'gets clients' do
51
+ client_ids = @toggl.clients.map { |c| c['id'] }
52
+ expect(client_ids).to match_array [ @client['id'], @client2['id'] ]
53
+ end
54
+
55
+ it 'gets workspace clients' do
56
+ client_ids = @toggl.clients(@workspace_id).map { |c| c['id'] }
57
+ expect(client_ids).to match_array [ @client['id'], @client2['id'] ]
58
+ end
59
+ end
60
+
61
+ it 'creates a client' do
62
+ expect(@client).to_not be nil
63
+ expect(@client['name']).to eq 'new client'
64
+ expect(@client['notes']).to eq nil
65
+ expect(@client['wid']).to eq @workspace_id
66
+ end
67
+
68
+ it 'gets client data' do
69
+ client = @toggl.get_client(@client['id'])
70
+ expect(client).to_not be nil
71
+ expect(client['name']).to eq @client['name']
72
+ expect(client['wid']).to eq @client['wid']
73
+ expect(client['notes']).to eq @client['notes']
74
+ expect(client['at']).to_not be nil
75
+ end
76
+
77
+ context 'client projects' do
78
+ it 'gets {} if there are no client projects' do
79
+ projects = @toggl.get_client_projects(@client['id'])
80
+ expect(projects).to be_empty
81
+ end
82
+
83
+ context 'new client projects' do
84
+ before :all do
85
+ @project = @toggl.create_project({ 'name' => 'project', 'wid' => @workspace_id, 'cid' => @client['id'] })
86
+ end
87
+
88
+ after :all do
89
+ TogglV8SpecHelper.delete_all_projects(@toggl)
90
+ end
91
+
92
+ it 'gets a client project' do
93
+ projects = @toggl.get_client_projects(@client['id'])
94
+ project_ids = projects.map { |p| p['id'] }
95
+ expect(project_ids).to eq [ @project['id'] ]
96
+ end
97
+
98
+ it 'gets multiple client projects' do
99
+ project2 = @toggl.create_project({ 'name' => 'project2', 'wid' => @workspace_id, 'cid' => @client['id'] })
100
+
101
+ projects = @toggl.get_client_projects(@client['id'])
102
+ project_ids = projects.map { |p| p['id'] }
103
+ expect(project_ids).to match_array [ @project['id'], project2['id'] ]
104
+
105
+ @toggl.delete_project(project2['id'])
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'updated client' do
112
+ before :each do
113
+ @client = @toggl.create_client({ 'name' => 'client to update', 'wid' => @workspace_id })
114
+ end
115
+
116
+ after :each do
117
+ @toggl.delete_client(@client['id'])
118
+ end
119
+
120
+ it 'updates client data' do
121
+ new_values = {
122
+ 'name' => 'CLIENT-NEW',
123
+ 'notes' => 'NOTES-NEW',
124
+ }
125
+
126
+ client = @toggl.update_client(@client['id'], new_values)
127
+ expect(client).to include(new_values)
128
+ end
129
+
130
+ it 'updates Pro project data', :pro_account do
131
+ new_values = {
132
+ 'hrate' => '7.77',
133
+ 'cur' => 'USD',
134
+ }
135
+ client = @toggl.update_client(@client['id'], new_values)
136
+ expect(client).to include(new_values)
137
+ end
138
+ end
139
+ end