ribose 0.3.0 → 0.4.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.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +27 -0
  3. data/.rubocop.yml +21 -16
  4. data/CHANGELOG.md +20 -0
  5. data/README.md +99 -0
  6. data/lib/ribose/actions/create.rb +2 -1
  7. data/lib/ribose/actions/update.rb +2 -1
  8. data/lib/ribose/calendar.rb +2 -2
  9. data/lib/ribose/client.rb +27 -9
  10. data/lib/ribose/configuration.rb +13 -4
  11. data/lib/ribose/connection.rb +15 -0
  12. data/lib/ribose/conversation.rb +18 -0
  13. data/lib/ribose/event.rb +86 -0
  14. data/lib/ribose/file_uploader.rb +20 -2
  15. data/lib/ribose/file_version.rb +97 -0
  16. data/lib/ribose/request.rb +49 -10
  17. data/lib/ribose/session.rb +36 -22
  18. data/lib/ribose/space.rb +4 -0
  19. data/lib/ribose/space_category.rb +15 -0
  20. data/lib/ribose/space_file.rb +29 -0
  21. data/lib/ribose/user.rb +9 -5
  22. data/lib/ribose/version.rb +1 -1
  23. data/lib/ribose/version_uploader.rb +27 -0
  24. data/lib/ribose.rb +3 -0
  25. data/ribose.gemspec +3 -4
  26. data/spec/fixtures/calendar_event.json +48 -0
  27. data/spec/fixtures/conversation.json +1 -1
  28. data/spec/fixtures/event_created.json +21 -0
  29. data/spec/fixtures/event_updated.json +23 -0
  30. data/spec/fixtures/file_version.json +14 -0
  31. data/spec/fixtures/space_categories.json +150 -0
  32. data/spec/fixtures/space_file_icon.json +4 -0
  33. data/spec/ribose/connection_spec.rb +11 -0
  34. data/spec/ribose/conversation_spec.rb +14 -0
  35. data/spec/ribose/event_spec.rb +85 -0
  36. data/spec/ribose/file_uploader_spec.rb +15 -2
  37. data/spec/ribose/file_version_spec.rb +76 -0
  38. data/spec/ribose/session_spec.rb +2 -2
  39. data/spec/ribose/space_category_spec.rb +13 -0
  40. data/spec/ribose/space_file_spec.rb +44 -0
  41. data/spec/ribose/space_spec.rb +11 -0
  42. data/spec/support/fake_ribose_api.rb +93 -5
  43. data/spec/support/file_upload_stub.rb +21 -15
  44. metadata +32 -34
  45. data/.travis.yml +0 -5
@@ -1,5 +1,7 @@
1
1
  module Ribose
2
2
  class Request
3
+
4
+ DEFAULT_CONTENT_TYPE = "application/json"
3
5
  # Initialize a Request
4
6
  #
5
7
  # @param http_method [Symbol] HTTP verb as sysmbol
@@ -20,8 +22,17 @@ module Ribose
20
22
  # @return [Sawyer::Resource]
21
23
  #
22
24
  def request(options = {})
25
+ parsable = extract_config_option(:parse) != false
23
26
  options[:query] = extract_config_option(:query) || {}
24
- agent.call(http_method, api_endpoint, data, options).data
27
+
28
+ response = agent.call(http_method, api_endpoint, data, options)
29
+
30
+ # update client headers from response
31
+ client.client_id = response.headers['client']
32
+ client.uid = response.headers['uid']
33
+ client.access_token = response.headers['access-token']
34
+
35
+ parsable == true ? response.data : response
25
36
  end
26
37
 
27
38
  # Make a HTTP GET Request
@@ -68,7 +79,7 @@ module Ribose
68
79
  attr_reader :client, :data, :http_method
69
80
 
70
81
  def ribose_host
71
- Ribose.configuration.api_host
82
+ Ribose.configuration.api_host.host
72
83
  end
73
84
 
74
85
  def extract_config_option(key)
@@ -78,13 +89,22 @@ module Ribose
78
89
  end
79
90
 
80
91
  def find_suitable_client
81
- client = extract_config_option(:client) || Ribose::Client.new
82
- client.is_a?(Ribose::Client) ? client: raise(Ribose::Unauthorized)
92
+ # client = extract_config_option(:client) || Ribose::Client.new
93
+ client = extract_config_option(:client) ||
94
+ Ribose::Client.from_login(
95
+ email: Ribose.configuration.user_email,
96
+ password: Ribose.configuration.user_password,
97
+ api_token: Ribose.configuration.api_token
98
+ )
99
+ client.is_a?(Ribose::Client) ? client : raise(Ribose::Unauthorized)
83
100
  end
84
101
 
85
102
  def require_auth_headers?
86
- auth_header = extract_config_option(:auth_header)
87
- auth_header == false ? false : true
103
+ extract_config_option(:auth_header) != false
104
+ end
105
+
106
+ def custom_content_headers
107
+ extract_config_option(:headers) || {}
88
108
  end
89
109
 
90
110
  def api_endpoint
@@ -95,9 +115,17 @@ module Ribose
95
115
  end
96
116
 
97
117
  def sawyer_options
118
+ faraday_options = { builder: custom_rack_builder }
119
+ unless Ribose.configuration.verify_ssl?
120
+ faraday_options.merge!(ssl: Faraday::SSLOptions.new(
121
+ false, nil, nil, OpenSSL::SSL::VERIFY_NONE
122
+ )
123
+ )
124
+ end
125
+
98
126
  {
99
127
  links_parser: Sawyer::LinkParsers::Simple.new,
100
- faraday: Faraday.new(builder: custom_rack_builder),
128
+ faraday: Faraday.new(faraday_options),
101
129
  }
102
130
  end
103
131
 
@@ -107,14 +135,25 @@ module Ribose
107
135
  end
108
136
  end
109
137
 
138
+ def set_content_type(headers)
139
+ header = custom_content_headers
140
+
141
+ headers[:content_type] = DEFAULT_CONTENT_TYPE
142
+ headers[:accept] = header.fetch(:accept, DEFAULT_CONTENT_TYPE)
143
+ end
144
+
110
145
  def agent
111
146
  @agent ||= Sawyer::Agent.new(ribose_host, sawyer_options) do |http|
112
- http.headers[:accept] = "application/json"
113
- http.headers[:content_type] = "application/json"
147
+ set_content_type(http.headers)
148
+
149
+ # set headers for devise-token-auth
150
+ http.headers["access-token"] = client.access_token
151
+ http.headers["client"] = client.client_id
152
+ http.headers["uid"] = client.uid
114
153
 
115
154
  if require_auth_headers?
116
155
  http.headers["X-Indigo-Token"] = client.api_token
117
- http.headers["X-Indigo-Email"] = client.user_email
156
+ http.headers["X-Indigo-Email"] = client.api_email
118
157
  end
119
158
  end
120
159
  end
@@ -1,40 +1,54 @@
1
1
  require "json"
2
- require "mechanize"
2
+ require 'net/http'
3
+ require 'uri'
3
4
  require "ribose/config"
4
5
 
5
6
  module Ribose
6
7
  class Session
7
- def initialize(username, password)
8
- @username = username
9
- @password = password
8
+ def initialize(username, password, api_email, api_token)
9
+ @username = username
10
+ @password = password
11
+ @api_email = api_email
12
+ @api_token = api_token
10
13
  end
11
14
 
12
15
  def create
13
- JSON.parse(authenticate_user)
14
- rescue NoMethodError, JSON::ParserError
15
- raise Ribose::Unauthorized
16
+ authenticate_user
16
17
  end
17
18
 
18
- def self.create(username:, password:)
19
- new(username, password).create
19
+ def self.create(username:, password:, api_email:, api_token:)
20
+ new(username, password, api_email, api_token).create
20
21
  end
21
22
 
22
23
  private
23
24
 
24
- attr_reader :username, :password
25
+ attr_reader :username, :password, :api_email, :api_token
25
26
 
26
27
  def authenticate_user
27
- page = agent.get(ribose_url_for("login"))
28
- find_and_submit_the_user_login_form(page)
29
- agent.get(ribose_url_for(["settings", "general", "info"])).body
30
- end
31
-
32
- def find_and_submit_the_user_login_form(page)
33
- login_form = page.form_with(id: "new_user")
34
- login_form.field_with(id: "loginEmail").value = username
35
- login_form.field_with(id: "loginPassword").value = password
36
-
37
- login_form.submit
28
+ uri = URI.parse(ribose_url_for("api/v2/auth/sign_in"))
29
+ response = Net::HTTP.start(
30
+ uri.host,
31
+ uri.port,
32
+ use_ssl: true,
33
+ verify_mode: Ribose.configuration.ssl_verification_mode
34
+ ) do |http|
35
+ request = Net::HTTP::Post.new(uri)
36
+ # set request headers
37
+ request['X-Indigo-Username'] = api_email
38
+ request['X-Indigo-Token'] = api_token
39
+ request['Content-Type'] = 'application/json'
40
+
41
+ # set form data
42
+ request.set_form_data(
43
+ 'username' => username,
44
+ 'password' => password
45
+ )
46
+ http.request(request)
47
+ end
48
+
49
+ # return response headers in hash if success
50
+ return response.each_header.to_h if response.is_a? Net::HTTPSuccess
51
+ nil
38
52
  end
39
53
 
40
54
  def agent
@@ -42,7 +56,7 @@ module Ribose
42
56
  end
43
57
 
44
58
  def ribose_url_for(*endpoint)
45
- [Ribose.configuration.web_url, *endpoint].join("/")
59
+ [Ribose.configuration.api_host, *endpoint].join("/")
46
60
  end
47
61
  end
48
62
  end
data/lib/ribose/space.rb CHANGED
@@ -15,6 +15,10 @@ module Ribose
15
15
  Ribose::Request.post("spaces/#{space_uuid}/freeze", options)
16
16
  end
17
17
 
18
+ def self.delete(space_uuid, confirmation:, **options)
19
+ remove(space_uuid, options.merge(password_confirmation: confirmation))
20
+ end
21
+
18
22
  private
19
23
 
20
24
  attr_reader :space
@@ -0,0 +1,15 @@
1
+ module Ribose
2
+ class SpaceCategory < Ribose::Base
3
+ include Ribose::Actions::All
4
+
5
+ private
6
+
7
+ def resources
8
+ nil
9
+ end
10
+
11
+ def resources_path
12
+ "space_categories"
13
+ end
14
+ end
15
+ end
@@ -34,6 +34,21 @@ module Ribose
34
34
  new(space_id: space_id, resource_id: file_id, **options).fetch
35
35
  end
36
36
 
37
+ # Download a space file
38
+ #
39
+ # @param space_id [UUID] The Space UUID
40
+ # @param file_id [Integer] The File Id
41
+ # @param options [Hash] Options as key and value pair.
42
+ #
43
+ # Two important keys are :version_id, and :output and
44
+ # if these are provided then it will use those otherwise
45
+ # it will do additional request to retirve those details
46
+ #
47
+ def self.download(space_id, file_id, options = {})
48
+ options[:version_id] ||= fetch(space_id, file_id).current_version_id
49
+ Ribose::FileVersion.download(space_id, file_id, **options)
50
+ end
51
+
37
52
  # Create a new file upload
38
53
  #
39
54
  # @param space_id [String] The Space UUID
@@ -66,6 +81,20 @@ module Ribose
66
81
  new(space_id: space_id, resource_id: file_id, **options).delete
67
82
  end
68
83
 
84
+ def fetch_icon
85
+ Ribose::Request.get([resource_path, "icon"].join("/"))
86
+ end
87
+
88
+ # Fetch a space file icon
89
+ #
90
+ # @param space_id [String] The Space UUID
91
+ # @param file_id [String] The space file ID
92
+ # @return [Sawyer::Resource]
93
+ #
94
+ def self.fetch_icon(space_id, file_id, options = {})
95
+ new(space_id: space_id, resource_id: file_id, **options).fetch_icon
96
+ end
97
+
69
98
  private
70
99
 
71
100
  attr_reader :space_id
data/lib/ribose/user.rb CHANGED
@@ -8,8 +8,12 @@ module Ribose
8
8
 
9
9
  def activate
10
10
  Ribose::Request.post(
11
- "signup.user",
12
- custom_option.merge(user: attributes, auth_header: false),
11
+ "api/v2/auth",
12
+ custom_option.merge(
13
+ user: attributes,
14
+ auth_header: false,
15
+ client: Ribose::Client.new
16
+ ),
13
17
  )
14
18
  end
15
19
 
@@ -17,12 +21,12 @@ module Ribose
17
21
  #
18
22
  # @param email [String] The registering user email
19
23
  # @param password [String] A strong password for login
20
- # @param otp [String] The OTP received via the email
24
+ # @param edata [String] The OTP received via the email
21
25
  # @param attributes [Hash] The other attributes as Hash.
22
26
  # @return [Sawyer::Resoruce] The newly activated user
23
27
  #
24
- def self.activate(email:, password:, otp:, **attributes)
25
- new(attributes.merge(email: email, password: password, otp: otp)).activate
28
+ def self.activate(email:, password:, edata:, **attributes)
29
+ new(attributes.merge(email: email, password: password, edata: edata)).activate
26
30
  end
27
31
 
28
32
  private
@@ -1,3 +1,3 @@
1
1
  module Ribose
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.4.1".freeze
3
3
  end
@@ -0,0 +1,27 @@
1
+ require "ribose/file_uploader"
2
+
3
+ module Ribose
4
+ class VersionUploader < Ribose::FileUploader
5
+ def initialize(space_id, file_id, file:, **attributes)
6
+ @file_id = file_id
7
+ super(space_id, file: file, **attributes)
8
+ end
9
+
10
+ def self.upload(space_id, file_id, file:, **attributes)
11
+ new(space_id, file_id, attributes.merge(file: file)).create
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :file_id
17
+
18
+ def notifiable_attributes(attributes, key)
19
+ attributes[:file_info_version] = attributes.delete(:file_info)
20
+ attributes.merge(key: key)
21
+ end
22
+
23
+ def space_file_path
24
+ ["spaces", space_id, "file", "files", file_id, "versions"].join("/")
25
+ end
26
+ end
27
+ end
data/lib/ribose.rb CHANGED
@@ -28,6 +28,9 @@ require "ribose/session"
28
28
  require "ribose/profile"
29
29
  require "ribose/wiki"
30
30
  require "ribose/member_role"
31
+ require "ribose/event"
32
+ require "ribose/space_category"
33
+ require "ribose/file_version"
31
34
 
32
35
  module Ribose
33
36
  def self.root
data/ribose.gemspec CHANGED
@@ -21,13 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.required_ruby_version = Gem::Requirement.new(">= 2.1.9")
22
22
 
23
23
  spec.add_dependency "id_pack", "~> 1.0.1"
24
- spec.add_dependency "mechanize", "~> 2.7.5"
25
24
  spec.add_dependency "mime-types", "~> 3.1"
26
25
  spec.add_dependency "sawyer", "~> 0.8.1"
27
26
 
28
- spec.add_development_dependency "bundler", "~> 1.14"
29
- spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "rake"
30
29
  spec.add_development_dependency "rspec", "~> 3.0"
31
- spec.add_development_dependency "pry", "~> 0.10.4"
30
+ spec.add_development_dependency "pry"
32
31
  spec.add_development_dependency "webmock", "~> 2.0"
33
32
  end
@@ -0,0 +1,48 @@
1
+ {
2
+ "filter": {
3
+ "enabled": [
4
+ 123456789
5
+ ],
6
+ "userSetting": [0]
7
+ },
8
+ "cal_info": [
9
+ {
10
+ "id": 123456789,
11
+ "owner_type": "User",
12
+ "name": "johndoe",
13
+ "owner_id": "2970d105-5ccc-4a8c",
14
+ "owner_name": "Personal",
15
+ "display_name": "johndoe (Personal)",
16
+ "can_manage": true,
17
+ "can_create_event": true
18
+ }
19
+ ],
20
+ "spaces_permission": {
21
+ "268b0407-c3a3-4aad-8693-fdba789f7f0d": false,
22
+ "457e8438-1c6f-423f-8bf2-77b9d5102fb0": true,
23
+ "52e47e18-9a9d-4663-94c5-abcb18fa783a": true,
24
+ "a45387e2-a573-48ba-8df0-f1424d8a8ec9": false,
25
+ "a7f7b94e-a007-4457-868f-5af319706ad5": false
26
+ },
27
+ "event": {
28
+ "id": 456789,
29
+ "name": "Sample event",
30
+ "description": "",
31
+ "where": "",
32
+ "all_day": true,
33
+ "recurring_type": "not_repeat",
34
+ "my_note": null,
35
+ "old_head_id": null,
36
+ "calendar_id": 123456789,
37
+ "created_by": "2970d105-5ccc-4a8c-b0c4-ec32d539a00a",
38
+ "utc_start": "2018-03-13T12:00:00.000Z",
39
+ "utc_finish": "2018-03-13T13:00:00.000Z",
40
+ "utc_old_start": null,
41
+ "utc_old_finish": null,
42
+ "can_save": true,
43
+ "can_delete": true,
44
+ "timestamp": 1520690119
45
+ },
46
+ "head_event": null,
47
+ "recurring": []
48
+ }
@@ -7,7 +7,7 @@
7
7
  "name": "Trips to the Mars!",
8
8
  "tag_list": [],
9
9
  "published": false,
10
- "is_favorite": false,
10
+ "is_favorite": true,
11
11
  "is_read": true,
12
12
  "allow_publish": false,
13
13
  "number_of_messages": 1,
@@ -0,0 +1,21 @@
1
+ {
2
+ "events": {
3
+ "id": 789012345,
4
+ "name": "Sample Event",
5
+ "description": "Sample event",
6
+ "where": "Skype",
7
+ "all_day": false,
8
+ "recurring_type": "not_repeat",
9
+ "my_note": null,
10
+ "old_head_id": null,
11
+ "calendar_id": 18987,
12
+ "created_by": "2970d105-5ccc",
13
+ "utc_start": "2018-04-04T18:00:00.000Z",
14
+ "utc_finish": "2018-04-04T18:30:00.000Z",
15
+ "utc_old_start": null,
16
+ "utc_old_finish": null,
17
+ "can_save": true,
18
+ "can_delete": true,
19
+ "timestamp": 1521387260
20
+ }
21
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": 345678901,
5
+ "name": "Sample event",
6
+ "description": "",
7
+ "where": "",
8
+ "all_day": true,
9
+ "recurring_type": "not_repeat",
10
+ "my_note": "",
11
+ "old_head_id": null,
12
+ "calendar_id": 123456789,
13
+ "created_by": "bdc6aced-f7d0-465d-9d8b",
14
+ "utc_start": "2017-11-07T12:00:00.000Z",
15
+ "utc_finish": "2017-11-07T13:00:00.000Z",
16
+ "utc_old_start": null,
17
+ "utc_old_finish": null,
18
+ "can_save": true,
19
+ "can_delete": true,
20
+ "timestamp": 1508401619
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 1,
3
+ "name": "sample.png",
4
+ "description": "",
5
+ "created_at": "2017-11-30T09:12:03.000+00:00",
6
+ "updated_at": "2017-11-30T09:12:05.000+00:00",
7
+ "position": 1,
8
+ "current_version_id": 789012,
9
+ "icon_path": "/spaces/456789/file/files/11570/versions/13235?type=usual",
10
+ "content_size": 81421,
11
+ "author": "John Doe",
12
+ "content_type": "image/png",
13
+ "file_info_id": 11570
14
+ }
@@ -0,0 +1,150 @@
1
+ [
2
+ {
3
+ "id": 1,
4
+ "name": "animals"
5
+ },
6
+ {
7
+ "id": 2,
8
+ "name": "architecture"
9
+ },
10
+ {
11
+ "id": 3,
12
+ "name": "art"
13
+ },
14
+ {
15
+ "id": 4,
16
+ "name": "business"
17
+ },
18
+ {
19
+ "id": 5,
20
+ "name": "cars_and_motorcycles"
21
+ },
22
+ {
23
+ "id": 6,
24
+ "name": "common_interest"
25
+ },
26
+ {
27
+ "id": 7,
28
+ "name": "diy_and_crafts"
29
+ },
30
+ {
31
+ "id": 8,
32
+ "name": "design"
33
+ },
34
+ {
35
+ "id": 9,
36
+ "name": "education"
37
+ },
38
+ {
39
+ "id": 10,
40
+ "name": "entertainment"
41
+ },
42
+ {
43
+ "id": 11,
44
+ "name": "fashion"
45
+ },
46
+ {
47
+ "id": 12,
48
+ "name": "film_music_books"
49
+ },
50
+ {
51
+ "id": 13,
52
+ "name": "food_and_drink"
53
+ },
54
+ {
55
+ "id": 14,
56
+ "name": "gardening"
57
+ },
58
+ {
59
+ "id": 15,
60
+ "name": "geek"
61
+ },
62
+ {
63
+ "id": 16,
64
+ "name": "geography"
65
+ },
66
+ {
67
+ "id": 17,
68
+ "name": "groups_and_organizations"
69
+ },
70
+ {
71
+ "id": 18,
72
+ "name": "hair_and_beauty"
73
+ },
74
+ {
75
+ "id": 19,
76
+ "name": "health_and_fitness"
77
+ },
78
+ {
79
+ "id": 20,
80
+ "name": "history"
81
+ },
82
+ {
83
+ "id": 21,
84
+ "name": "holidays_and_events"
85
+ },
86
+ {
87
+ "id": 22,
88
+ "name": "home_decor"
89
+ },
90
+ {
91
+ "id": 23,
92
+ "name": "humor"
93
+ },
94
+ {
95
+ "id": 24,
96
+ "name": "illustrations_and_posters"
97
+ },
98
+ {
99
+ "id": 25,
100
+ "name": "kids"
101
+ },
102
+ {
103
+ "id": 26,
104
+ "name": "mens_fashion"
105
+ },
106
+ {
107
+ "id": 27,
108
+ "name": "outdoors"
109
+ },
110
+ {
111
+ "id": 28,
112
+ "name": "philosophy"
113
+ },
114
+ {
115
+ "id": 29,
116
+ "name": "photography"
117
+ },
118
+ {
119
+ "id": 30,
120
+ "name": "products"
121
+ },
122
+ {
123
+ "id": 31,
124
+ "name": "religion_and_spirituality"
125
+ },
126
+ {
127
+ "id": 32,
128
+ "name": "science_and_nature"
129
+ },
130
+ {
131
+ "id": 33,
132
+ "name": "sports"
133
+ },
134
+ {
135
+ "id": 34,
136
+ "name": "technology"
137
+ },
138
+ {
139
+ "id": 35,
140
+ "name": "travel"
141
+ },
142
+ {
143
+ "id": 36,
144
+ "name": "weddings"
145
+ },
146
+ {
147
+ "id": 37,
148
+ "name": "others"
149
+ }
150
+ ]
@@ -0,0 +1,4 @@
1
+ {
2
+ "icon_processed": true,
3
+ "icon_path": "/spaces/files/icon_path?type=usual"
4
+ }
@@ -23,4 +23,15 @@ RSpec.describe Ribose::Connection do
23
23
  expect(suggestions.first.name).to eq("Jennie Doe")
24
24
  end
25
25
  end
26
+
27
+ describe ".disconnect" do
28
+ it "disconnect with provided connection" do
29
+ connection_id = 123_456
30
+ stub_ribose_connection_delete_api(connection_id)
31
+
32
+ expect do
33
+ Ribose::Connection.disconnect(connection_id)
34
+ end.not_to raise_error
35
+ end
36
+ end
26
37
  end
@@ -59,6 +59,20 @@ RSpec.describe Ribose::Conversation do
59
59
  end
60
60
  end
61
61
 
62
+ describe ".mark_as_favorite" do
63
+ it "marks a conversation as favorite" do
64
+ space_id = 123_456_789
65
+ conversation_id = 456_789
66
+ stub_ribose_space_conversation_mafav_api(space_id, conversation_id)
67
+
68
+ conversation = Ribose::Conversation.mark_as_favorite(
69
+ space_id, conversation_id
70
+ )
71
+
72
+ expect(conversation.is_favorite).to eq(true)
73
+ end
74
+ end
75
+
62
76
  describe ".destroy" do
63
77
  it "remvoes a conversation from a space" do
64
78
  space_id = 123456789