decidim-api 0.30.3 → 0.31.0.rc2
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 +65 -11
|
@@ -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
|