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.
- checksums.yaml +4 -4
- data/app/controllers/decidim/api/application_controller.rb +20 -0
- data/app/controllers/decidim/api/queries_controller.rb +33 -1
- data/app/controllers/decidim/api/sessions_controller.rb +61 -0
- data/app/models/decidim/api/api_user.rb +82 -0
- data/app/models/decidim/api/jwt_denylist.rb +11 -0
- data/app/packs/entrypoints/decidim_api_graphiql.js +2 -1
- data/app/presenters/decidim/api/api_user_presenter.rb +23 -0
- data/config/assets.rb +2 -2
- data/config/initializers/devise.rb +26 -0
- data/config/routes.rb +8 -0
- data/decidim-api.gemspec +2 -1
- data/docs/usage.md +98 -8
- data/lib/decidim/api/component_mutation_type.rb +19 -0
- data/lib/decidim/api/devise.rb +12 -0
- data/lib/decidim/api/engine.rb +2 -2
- data/lib/decidim/api/graphql_permissions.rb +125 -0
- data/lib/decidim/api/mutation_type.rb +10 -0
- data/lib/decidim/api/required_scopes.rb +31 -0
- data/lib/decidim/api/test/component_context.rb +17 -18
- data/lib/decidim/api/test/factories.rb +33 -0
- data/lib/decidim/api/test/mutation_context.rb +38 -0
- data/lib/decidim/api/test/shared_examples/amendable_interface_examples.rb +14 -0
- data/lib/decidim/api/test/shared_examples/amendable_proposals_interface_examples.rb +50 -0
- data/lib/decidim/api/test/shared_examples/attachable_interface_examples.rb +40 -0
- data/lib/decidim/api/test/shared_examples/authorable_interface_examples.rb +46 -0
- data/lib/decidim/api/test/shared_examples/categories_container_examples.rb +22 -0
- data/lib/decidim/api/test/shared_examples/categorizable_interface_examples.rb +27 -0
- data/lib/decidim/api/test/shared_examples/coauthorable_interface_examples.rb +77 -0
- data/lib/decidim/api/test/shared_examples/commentable_interface_examples.rb +13 -0
- data/lib/decidim/api/test/shared_examples/fingerprintable_interface_examples.rb +17 -0
- data/lib/decidim/api/test/shared_examples/followable_interface_examples.rb +13 -0
- data/lib/decidim/api/test/shared_examples/input_filter_examples.rb +77 -0
- data/lib/decidim/api/test/shared_examples/input_sort_examples.rb +126 -0
- data/lib/decidim/api/test/shared_examples/likeable_interface_examples.rb +22 -0
- data/lib/decidim/api/test/shared_examples/localizable_interface_examples.rb +29 -0
- data/lib/decidim/api/test/shared_examples/participatory_space_resourcable_interface_examples.rb +61 -0
- data/lib/decidim/api/test/shared_examples/referable_interface_examples.rb +13 -0
- data/lib/decidim/api/test/shared_examples/scopable_interface_examples.rb +19 -0
- data/lib/decidim/api/test/shared_examples/statistics_examples.rb +30 -16
- data/lib/decidim/api/test/shared_examples/taxonomizable_interface_examples.rb +20 -0
- data/lib/decidim/api/test/shared_examples/timestamps_interface_examples.rb +21 -0
- data/lib/decidim/api/test/shared_examples/traceable_interface_examples.rb +49 -0
- data/lib/decidim/api/test/type_context.rb +9 -1
- data/lib/decidim/api/test.rb +22 -0
- data/lib/decidim/api/types/base_mutation.rb +5 -1
- data/lib/decidim/api/types/base_object.rb +4 -69
- data/lib/decidim/api/types.rb +3 -0
- data/lib/decidim/api/version.rb +1 -1
- data/lib/decidim/api.rb +25 -5
- data/lib/devise/models/api_authenticatable.rb +30 -0
- data/lib/devise/strategies/api_authenticatable.rb +21 -0
- data/lib/warden/jwt_auth/decidim_overrides.rb +42 -0
- 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
|
data/lib/decidim/api/test/shared_examples/participatory_space_resourcable_interface_examples.rb
ADDED
@@ -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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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(
|
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
|
)
|
data/lib/decidim/api/test.rb
CHANGED
@@ -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
|