decidim-api 0.30.2 → 0.31.0.rc1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/decidim/api/application_controller.rb +20 -0
  3. data/app/controllers/decidim/api/queries_controller.rb +33 -1
  4. data/app/controllers/decidim/api/sessions_controller.rb +61 -0
  5. data/app/models/decidim/api/api_user.rb +82 -0
  6. data/app/models/decidim/api/jwt_denylist.rb +11 -0
  7. data/app/packs/entrypoints/decidim_api_graphiql.js +2 -1
  8. data/app/presenters/decidim/api/api_user_presenter.rb +23 -0
  9. data/config/assets.rb +2 -2
  10. data/config/initializers/devise.rb +26 -0
  11. data/config/routes.rb +8 -0
  12. data/decidim-api.gemspec +2 -1
  13. data/docs/usage.md +98 -8
  14. data/lib/decidim/api/component_mutation_type.rb +19 -0
  15. data/lib/decidim/api/devise.rb +12 -0
  16. data/lib/decidim/api/engine.rb +2 -2
  17. data/lib/decidim/api/graphql_permissions.rb +125 -0
  18. data/lib/decidim/api/mutation_type.rb +10 -0
  19. data/lib/decidim/api/required_scopes.rb +31 -0
  20. data/lib/decidim/api/test/component_context.rb +17 -18
  21. data/lib/decidim/api/test/factories.rb +33 -0
  22. data/lib/decidim/api/test/mutation_context.rb +38 -0
  23. data/lib/decidim/api/test/shared_examples/amendable_interface_examples.rb +14 -0
  24. data/lib/decidim/api/test/shared_examples/amendable_proposals_interface_examples.rb +50 -0
  25. data/lib/decidim/api/test/shared_examples/attachable_interface_examples.rb +40 -0
  26. data/lib/decidim/api/test/shared_examples/authorable_interface_examples.rb +46 -0
  27. data/lib/decidim/api/test/shared_examples/categories_container_examples.rb +22 -0
  28. data/lib/decidim/api/test/shared_examples/categorizable_interface_examples.rb +27 -0
  29. data/lib/decidim/api/test/shared_examples/coauthorable_interface_examples.rb +77 -0
  30. data/lib/decidim/api/test/shared_examples/commentable_interface_examples.rb +13 -0
  31. data/lib/decidim/api/test/shared_examples/fingerprintable_interface_examples.rb +17 -0
  32. data/lib/decidim/api/test/shared_examples/followable_interface_examples.rb +13 -0
  33. data/lib/decidim/api/test/shared_examples/input_filter_examples.rb +77 -0
  34. data/lib/decidim/api/test/shared_examples/input_sort_examples.rb +126 -0
  35. data/lib/decidim/api/test/shared_examples/likeable_interface_examples.rb +22 -0
  36. data/lib/decidim/api/test/shared_examples/localizable_interface_examples.rb +29 -0
  37. data/lib/decidim/api/test/shared_examples/participatory_space_resourcable_interface_examples.rb +61 -0
  38. data/lib/decidim/api/test/shared_examples/referable_interface_examples.rb +13 -0
  39. data/lib/decidim/api/test/shared_examples/scopable_interface_examples.rb +19 -0
  40. data/lib/decidim/api/test/shared_examples/statistics_examples.rb +30 -16
  41. data/lib/decidim/api/test/shared_examples/taxonomizable_interface_examples.rb +20 -0
  42. data/lib/decidim/api/test/shared_examples/timestamps_interface_examples.rb +21 -0
  43. data/lib/decidim/api/test/shared_examples/traceable_interface_examples.rb +49 -0
  44. data/lib/decidim/api/test/type_context.rb +9 -1
  45. data/lib/decidim/api/test.rb +22 -0
  46. data/lib/decidim/api/types/base_mutation.rb +5 -1
  47. data/lib/decidim/api/types/base_object.rb +4 -69
  48. data/lib/decidim/api/types.rb +3 -0
  49. data/lib/decidim/api/version.rb +1 -1
  50. data/lib/decidim/api.rb +25 -5
  51. data/lib/devise/models/api_authenticatable.rb +30 -0
  52. data/lib/devise/strategies/api_authenticatable.rb +21 -0
  53. data/lib/warden/jwt_auth/decidim_overrides.rb +42 -0
  54. metadata +66 -12
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "followable interface" do
6
+ describe "follows_count" do
7
+ let(:query) { "{ followsCount }" }
8
+
9
+ it "includes the field" do
10
+ expect(response["followsCount"]).to eq(model.follows_count)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for "collection has before/since input filter" do |collection, field|
4
+ context "when date is before the past" do
5
+ let(:query) { %[{ #{collection}(filter: {#{field}Before: "#{2.days.ago.to_date}"}) { id } }] }
6
+
7
+ it "finds nothing" do
8
+ ids = response[collection].map { |item| item["id"] }
9
+ expect(ids).to eq([])
10
+ end
11
+ end
12
+
13
+ context "when date is before the future" do
14
+ let(:query) { %[{ #{collection}(filter: {#{field}Before: "#{2.days.from_now.to_date}"}) { id } }] }
15
+
16
+ it "finds the models " do
17
+ ids = response[collection].map { |item| item["id"] }
18
+ expect(ids).to match_array(models.map(&:id).map(&:to_s))
19
+ end
20
+ end
21
+
22
+ context "when date is after the future" do
23
+ let(:query) { %[{ #{collection}(filter: {#{field}Since: "#{2.days.from_now.to_date}"}) { id } }] }
24
+
25
+ it "finds nothing " do
26
+ ids = response[collection].map { |item| item["id"] }
27
+ expect(ids).to eq([])
28
+ end
29
+ end
30
+
31
+ context "when date is after the past" do
32
+ let(:query) { %[{ #{collection}(filter: {#{field}Since: "#{2.days.ago.to_date}"}) { id } }] }
33
+
34
+ it "finds the models " do
35
+ ids = response[collection].map { |item| item["id"] }
36
+ expect(ids).to match_array(models.map(&:id).map(&:to_s))
37
+ end
38
+ end
39
+ end
40
+
41
+ shared_examples_for "connection has before/since input filter" do |connection, field|
42
+ context "when date is before the past" do
43
+ let(:query) { %[{ #{connection}(filter: {#{field}Before: "#{2.days.ago.to_date}"}) { edges { node { id } } } }] }
44
+
45
+ it "finds nothing" do
46
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
47
+ expect(ids).to eq([])
48
+ end
49
+ end
50
+
51
+ context "when date is before the future" do
52
+ let(:query) { %[{ #{connection}(filter: {#{field}Before: "#{2.days.from_now.to_date}"}) { edges { node { id } } } }] }
53
+
54
+ it "finds the models " do
55
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
56
+ expect(ids).to match_array(models.map(&:id).map(&:to_s))
57
+ end
58
+ end
59
+
60
+ context "when date is after the future" do
61
+ let(:query) { %[{ #{connection}(filter: {#{field}Since: "#{2.days.from_now.to_date}"}) { edges { node { id } } } }] }
62
+
63
+ it "finds nothing " do
64
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
65
+ expect(ids).to eq([])
66
+ end
67
+ end
68
+
69
+ context "when date is after the past" do
70
+ let(:query) { %[{ #{connection}(filter: {#{field}Since: "#{2.days.ago.to_date}"}) { edges { node { id } } } }] }
71
+
72
+ it "finds the models " do
73
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
74
+ expect(ids).to match_array(models.map(&:id).map(&:to_s))
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for "collection has input sort" do |collection, field|
4
+ describe "ASC" do
5
+ let(:query) { %[{ #{collection}(order: {#{field}: "ASC"}) { id } }] }
6
+
7
+ it "returns expected order" do
8
+ ids = response[collection].map { |item| item["id"] }
9
+ replies_ids = models.sort_by(&field.underscore.to_sym).map(&:id).map(&:to_s)
10
+ expect(ids).to eq(replies_ids)
11
+ expect(ids).not_to eq(replies_ids.reverse)
12
+ end
13
+ end
14
+
15
+ describe "DESC" do
16
+ let(:query) { %[{ #{collection}(order: {#{field}: "DESC"}) { id } }] }
17
+
18
+ it "returns reversed order" do
19
+ ids = response[collection].map { |item| item["id"] }
20
+ replies_ids = models.sort_by(&field.underscore.to_sym).map(&:id).map(&:to_s)
21
+ expect(ids).not_to eq(replies_ids)
22
+ expect(ids).to eq(replies_ids.reverse)
23
+ end
24
+ end
25
+ end
26
+
27
+ shared_examples_for "collection has i18n input sort" do |collection, field|
28
+ context "when locale is not specified" do
29
+ describe "ASC" do
30
+ let(:query) { %[{ #{collection}(order: { #{field}: "ASC" }) { id } }] }
31
+
32
+ it "returns alphabetical order" do
33
+ response_ids = response[collection].map { |item| item["id"].to_i }
34
+ ids = models.sort_by { |item| item.public_send(field.to_sym)[current_organization.default_locale] }.map { |item| item.id.to_i }
35
+ expect(response_ids).to eq(ids)
36
+ expect(response_ids).not_to eq(ids.reverse)
37
+ end
38
+ end
39
+
40
+ describe "DESC" do
41
+ let(:query) { %[{ #{collection}(order: { #{field}: "DESC" }) { id } }] }
42
+
43
+ it "returns revered alphabetical order" do
44
+ response_ids = response[collection].map { |item| item["id"].to_i }
45
+ ids = models.sort_by { |item| item.public_send(field.to_sym)[current_organization.default_locale] }.map { |item| item.id.to_i }
46
+ expect(response_ids).not_to eq(ids)
47
+ expect(response_ids).to eq(ids.reverse)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "when locale is specified" do
53
+ describe "ASC" do
54
+ let(:query) { %[{ #{collection}(order: { #{field}: "ASC", locale: "ca" }) { id } }] }
55
+
56
+ it "returns alphabetical order" do
57
+ response_ids = response[collection].map { |item| item["id"].to_i }
58
+ ids = models.sort_by { |item| item.public_send(field.to_sym)["ca"] }.map { |item| item.id.to_i }
59
+ expect(response_ids).to eq(ids)
60
+ expect(response_ids).not_to eq(ids.reverse)
61
+ end
62
+ end
63
+
64
+ describe "DESC" do
65
+ let(:query) { %[{ #{collection}(order: { #{field}: "DESC", locale: "ca" }) { id } }] }
66
+
67
+ it "returns revered alphabetical order" do
68
+ response_ids = response[collection].map { |item| item["id"].to_i }
69
+ ids = models.sort_by { |item| item.public_send(field.to_sym)["ca"] }.map { |item| item.id.to_i }
70
+ expect(response_ids).not_to eq(ids)
71
+ expect(response_ids).to eq(ids.reverse)
72
+ end
73
+ end
74
+
75
+ context "when locale does not exist in the organization" do
76
+ let(:query) { %[{ #{collection}(order: { #{field}: "DESC", locale: "de" }) { id } }] }
77
+
78
+ it "returns all the component ordered" do
79
+ expect { response }.to raise_exception(Exception)
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ shared_examples_for "connection has input sort" do |connection, field|
86
+ describe "ASC" do
87
+ let(:query) { %[{ #{connection}(order: {#{field}: "ASC"}) { edges { node { id } } } }] }
88
+
89
+ it "returns expected order" do
90
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
91
+ replies_ids = models.sort_by(&field.underscore.to_sym).map(&:id).map(&:to_s)
92
+ expect(ids).to eq(replies_ids)
93
+ end
94
+ end
95
+
96
+ describe "DESC" do
97
+ let(:query) { %[{ #{connection}(order: {#{field}: "DESC"}) { edges { node { id } } } }] }
98
+
99
+ it "returns reversed order" do
100
+ ids = response[connection]["edges"].map { |edge| edge["node"]["id"] }
101
+ replies_ids = models.sort_by(&field.underscore.to_sym).map(&:id).map(&:to_s)
102
+ expect(ids).to eq(replies_ids.reverse)
103
+ end
104
+ end
105
+ end
106
+
107
+ # This example requires a let!(:most_liked)
108
+ shared_examples_for "connection has like_count sort" do |connection|
109
+ describe "ASC" do
110
+ let(:query) { %[{ #{connection}(order: {likeCount: "ASC"}) { edges { node { id } } } }] }
111
+
112
+ it "returns the most liked last" do
113
+ expect(response[connection]["edges"].count).to eq(4)
114
+ expect(response[connection]["edges"].last["node"]["id"]).to eq(most_liked.id.to_s)
115
+ end
116
+ end
117
+
118
+ describe "DESC" do
119
+ let(:query) { %[{ #{connection}(order: {likeCount: "DESC"}) { edges { node { id } } } }] }
120
+
121
+ it "returns the most liked first" do
122
+ expect(response[connection]["edges"].count).to eq(4)
123
+ expect(response[connection]["edges"].first["node"]["id"]).to eq(most_liked.id.to_s)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "likeable interface" do
6
+ describe "likesCount" do
7
+ let(:query) { "{ likesCount }" }
8
+
9
+ it "returns the amount of likes for this query" do
10
+ expect(response["likesCount"]).to eq(model.likes.count)
11
+ end
12
+ end
13
+
14
+ describe "likes" do
15
+ let(:query) { "{ likes { name } }" }
16
+
17
+ it "returns the likes this query has received" do
18
+ like_names = response["likes"].map { |like| like["name"] }
19
+ expect(like_names).to include(*model.likes.map(&:author).map(&:name))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "localizable interface" do
6
+ describe "address" do
7
+ let(:query) { "{ address }" }
8
+
9
+ it "returns the address of this proposal" do
10
+ expect(response["address"]).to eq(model.address)
11
+ end
12
+ end
13
+
14
+ describe "coordinates" do
15
+ let(:query) { "{ coordinates { latitude longitude } }" }
16
+
17
+ before do
18
+ model.latitude = 2
19
+ model.longitude = 40
20
+ model.save!
21
+ end
22
+ it "returns the meeting's address" do
23
+ expect(response["coordinates"]).to include(
24
+ "latitude" => model.latitude,
25
+ "longitude" => model.longitude
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "participatory space resourcable interface" do
6
+ let!(:process1) { create(:participatory_process, organization: model.organization) }
7
+ let!(:process2) { create(:participatory_process, organization: model.organization) }
8
+ let!(:process3) { create(:participatory_process, organization: model.organization) }
9
+ let!(:model) { create(:assembly) }
10
+
11
+ context "when checks is has steps" do
12
+ describe "hasSteps" do
13
+ let(:query) { "{hasSteps}" }
14
+
15
+ it "has the field" do
16
+ expect(response["hasSteps"]).to be(false)
17
+ end
18
+ end
19
+
20
+ describe "allows_steps" do
21
+ let(:query) { "{allowsSteps}" }
22
+
23
+ it "has the field" do
24
+ expect(response["allowsSteps"]).to be(false)
25
+ end
26
+ end
27
+ end
28
+
29
+ context "when linked from the model" do
30
+ describe "linkedParticipatorySpaces" do
31
+ let(:query) { "{ linkedParticipatorySpaces { participatorySpace { id } } }" }
32
+
33
+ before do
34
+ model.link_participatory_space_resources([process1, process2], :included_participatory_processes)
35
+ end
36
+
37
+ it "includes the linked resources" do
38
+ ids = response["linkedParticipatorySpaces"].map { |l| l["participatorySpace"]["id"] }
39
+ expect(ids).to include(process1.id.to_s, process2.id.to_s)
40
+ expect(ids).not_to include(process3.id.to_s)
41
+ end
42
+ end
43
+ end
44
+
45
+ context "when linked towards the model" do
46
+ describe "linkedParticipatorySpaces" do
47
+ let(:query) { "{ linkedParticipatorySpaces { participatorySpace { id } } }" }
48
+
49
+ before do
50
+ process1.link_participatory_space_resources(model, :included_participatory_processes)
51
+ process2.link_participatory_space_resources(model, :included_participatory_processes)
52
+ end
53
+
54
+ it "includes the linked resources" do
55
+ ids = response["linkedParticipatorySpaces"].map { |l| l["participatorySpace"]["id"] }
56
+ expect(ids).to include(process1.id.to_s, process2.id.to_s)
57
+ expect(ids).not_to include(process3.id.to_s)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "referable interface" do
6
+ describe "reference" do
7
+ let(:query) { "{ reference }" }
8
+
9
+ it "includes the field" do
10
+ expect(response["reference"]).to eq(model.reference.to_s)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "scopable interface" do
6
+ let!(:scope) { create(:scope, organization: model.participatory_space.organization) }
7
+
8
+ before do
9
+ model.update(scope:)
10
+ end
11
+
12
+ describe "scope" do
13
+ let(:query) { "{ scope { id } }" }
14
+
15
+ it "has a scope" do
16
+ expect(response).to include("scope" => { "id" => scope.id.to_s })
17
+ end
18
+ end
19
+ end
@@ -4,21 +4,21 @@ require "spec_helper"
4
4
 
5
5
  shared_examples "implements stats type" do
6
6
  context "when the space implements stats" do
7
- let(:expected_data) do
8
- {
9
- "stats" => [
10
- { "name" => "dummies_count_high", "value" => 0 },
11
- { "name" => "pages_count", "value" => 0 },
12
- { "name" => "proposals_count", "value" => 0 },
13
- { "name" => "meetings_count", "value" => 0 },
14
- { "name" => "budgets_count", "value" => 0 },
15
- { "name" => "surveys_count", "value" => 0 },
16
- { "name" => "results_count", "value" => 0 },
17
- { "name" => "debates_count", "value" => 0 },
18
- { "name" => "sortitions_count", "value" => 0 },
19
- { "name" => "posts_count", "value" => 0 }
20
- ]
21
- }
7
+ before do
8
+ allow(Decidim::ParticipatoryProcesses::ParticipatoryProcessStatsPresenter).to receive(:new)
9
+ .and_return(double(collection: [
10
+ { name: "dummies_count_high", data: [0], tooltip_key: "dummies_count_high_tooltip" },
11
+ { name: "pages_count", data: [0], tooltip_key: "pages_count_tooltip" },
12
+ { name: "proposals_count", data: [0], tooltip_key: "proposals_count_tooltip" },
13
+ { name: "meetings_count", data: [0], tooltip_key: "meetings_count_tooltip" },
14
+ { name: "projects_count", data: [0], tooltip_key: "budgets_count_tooltip" },
15
+ { name: "surveys_count", data: [0], tooltip_key: "surveys_count_tooltip" },
16
+ { name: "results_count", data: [0], tooltip_key: "results_count_tooltip" },
17
+ { name: "debates_count", data: [0], tooltip_key: "debates_count_tooltip" },
18
+ { name: "sortitions_count", data: [0], tooltip_key: "sortitions_count_tooltip" },
19
+ { name: "posts_count", data: [0], tooltip_key: "posts_count_tooltip" },
20
+ { name: "collaborative_texts_count", data: [0], tooltip_key: "collaborative_texts_count_tooltip" }
21
+ ]))
22
22
  end
23
23
 
24
24
  it "executes successfully" do
@@ -26,7 +26,21 @@ shared_examples "implements stats type" do
26
26
  end
27
27
 
28
28
  it "returns the correct response" do
29
- expect(stats_response).to match_array(expected_data["stats"])
29
+ expect(stats_response).to match_array(
30
+ [
31
+ { "name" => { "translation" => "Dummies high" }, "value" => 0 },
32
+ { "name" => { "translation" => "Pages" }, "value" => 0 },
33
+ { "name" => { "translation" => "Proposals" }, "value" => 0 },
34
+ { "name" => { "translation" => "Meetings" }, "value" => 0 },
35
+ { "name" => { "translation" => "Budgets" }, "value" => 0 },
36
+ { "name" => { "translation" => "Surveys" }, "value" => 0 },
37
+ { "name" => { "translation" => "Results" }, "value" => 0 },
38
+ { "name" => { "translation" => "Debates" }, "value" => 0 },
39
+ { "name" => { "translation" => "Sortitions" }, "value" => 0 },
40
+ { "name" => { "translation" => "Posts" }, "value" => 0 },
41
+ { "name" => { "translation" => "Collaborative texts" }, "value" => 0 }
42
+ ]
43
+ )
30
44
  end
31
45
  end
32
46
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "taxonomizable interface" do
6
+ let!(:root_taxonomy) { create(:taxonomy, organization:) }
7
+ let!(:taxonomy) { create(:taxonomy, parent: root_taxonomy, organization:) }
8
+
9
+ before do
10
+ model.update(taxonomies: [taxonomy])
11
+ end
12
+
13
+ describe "taxonomies" do
14
+ let(:query) { "{ taxonomies { id } }" }
15
+
16
+ it "has taxonomies" do
17
+ expect(response).to include("taxonomies" => [{ "id" => taxonomy.id.to_s }])
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "timestamps interface" do
6
+ describe "createdAt" do
7
+ let(:query) { "{ createdAt }" }
8
+
9
+ it "returns when was this query created at" do
10
+ expect(response["createdAt"]).to eq(model.created_at.to_time.iso8601)
11
+ end
12
+ end
13
+
14
+ describe "updatedAt" do
15
+ let(:query) { "{ updatedAt }" }
16
+
17
+ it "returns when was this query updated at" do
18
+ expect(response["updatedAt"]).to eq(model.updated_at.to_time.iso8601)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "traceable interface" do
6
+ let(:field) { :title }
7
+
8
+ describe "traceable", versioning: true do
9
+ let(:version_author) { try(:author) || model.try(:creator_identity) || model.try(:normalized_author) }
10
+
11
+ before { Decidim.traceability.update!(model, version_author, field => { en: "test" }) }
12
+
13
+ context "when field createdAt" do
14
+ let(:query) { "{ versions { createdAt } }" }
15
+
16
+ it "returns created_at field of the version to iso format" do
17
+ dates = response["versions"].map { |version| version["createdAt"] }
18
+ expect(dates).to include(*model.versions.map { |version| version.created_at.to_time.iso8601 })
19
+ end
20
+ end
21
+
22
+ context "when field id" do
23
+ let(:query) { "{ versions { id } }" }
24
+
25
+ it "returns ID field of the version" do
26
+ ids = response["versions"].map { |version| version["id"].to_i }
27
+ expect(ids).to include(*model.versions.map(&:id))
28
+ end
29
+ end
30
+
31
+ context "when field editor" do
32
+ let(:query) { "{ versions { editor { name } } }" }
33
+
34
+ it "returns editor field of the versions" do
35
+ editors = response["versions"].map { |version| version["editor"]["name"] if version["editor"] }
36
+ expect(editors).to include(*model.versions.map { |version| version.editor.name if version.respond_to? :editor })
37
+ end
38
+ end
39
+
40
+ context "when field changeset" do
41
+ let(:query) { "{ versions { changeset } }" }
42
+
43
+ it "returns changeset field of the versions" do
44
+ changesets = response["versions"].map { |version| version["changeset"] }
45
+ expect(changesets).to include(*model.versions.map(&:changeset))
46
+ end
47
+ end
48
+ end
49
+ end
@@ -4,6 +4,13 @@ shared_context "with a graphql class type" do
4
4
  let!(:current_organization) { create(:organization) }
5
5
  let!(:current_user) { create(:user, :confirmed, organization: current_organization) }
6
6
  let!(:current_component) { create(:component) }
7
+ let(:api_scopes) do
8
+ if current_user.present?
9
+ Doorkeeper::OAuth::Scopes.from_array(Doorkeeper.config.scopes.all)
10
+ else
11
+ Doorkeeper::OAuth::Scopes.from_string("api:read")
12
+ end
13
+ end
7
14
  let(:model) { OpenStruct.new({}) }
8
15
  let(:type_class) { described_class }
9
16
  let(:variables) { {} }
@@ -28,7 +35,8 @@ shared_context "with a graphql class type" do
28
35
  context: {
29
36
  current_organization:,
30
37
  current_user:,
31
- current_component:
38
+ current_component:,
39
+ scopes: api_scopes
32
40
  },
33
41
  variables:
34
42
  )
@@ -1,3 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decidim/api/test/component_context"
4
+ require "decidim/api/test/shared_examples/amendable_interface_examples"
5
+ require "decidim/api/test/shared_examples/amendable_proposals_interface_examples"
6
+ require "decidim/api/test/shared_examples/attachable_interface_examples"
7
+ require "decidim/api/test/shared_examples/authorable_interface_examples"
8
+ require "decidim/api/test/shared_examples/categories_container_examples"
9
+ require "decidim/api/test/shared_examples/categorizable_interface_examples"
10
+ require "decidim/api/test/shared_examples/coauthorable_interface_examples"
11
+ require "decidim/api/test/shared_examples/commentable_interface_examples"
12
+ require "decidim/api/test/shared_examples/fingerprintable_interface_examples"
13
+ require "decidim/api/test/shared_examples/followable_interface_examples"
14
+ require "decidim/api/test/shared_examples/input_filter_examples"
15
+ require "decidim/api/test/shared_examples/input_sort_examples"
16
+ require "decidim/api/test/shared_examples/likeable_interface_examples"
17
+ require "decidim/api/test/shared_examples/localizable_interface_examples"
18
+ require "decidim/api/test/shared_examples/participatory_space_resourcable_interface_examples"
19
+ require "decidim/api/test/shared_examples/referable_interface_examples"
20
+ require "decidim/api/test/shared_examples/scopable_interface_examples"
3
21
  require "decidim/api/test/shared_examples/statistics_examples"
22
+ require "decidim/api/test/shared_examples/taxonomizable_interface_examples"
23
+ require "decidim/api/test/shared_examples/timestamps_interface_examples"
24
+ require "decidim/api/test/shared_examples/traceable_interface_examples"
25
+ require "decidim/api/test/type_context"
@@ -4,9 +4,13 @@ module Decidim
4
4
  module Api
5
5
  module Types
6
6
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
7
+ include Decidim::Api::GraphqlPermissions
8
+
7
9
  object_class BaseObject
8
- field_class BaseField
10
+ field_class Types::BaseField
9
11
  input_object_class BaseInputObject
12
+
13
+ required_scopes "api:read", "api:write"
10
14
  end
11
15
  end
12
16
  end