forest_liana 9.20.0 → 9.20.2
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/forest_liana/actions_controller.rb +9 -4
- data/app/services/forest_liana/schema_adapter.rb +5 -1
- data/config/routes/actions.rb +2 -3
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/lib/forest_liana/collections/island.rb +13 -0
- data/spec/requests/actions_controller_spec.rb +19 -0
- data/spec/requests/workflow_executions_spec.rb +50 -0
- data/test/services/forest_liana/schema_adapter_test.rb +56 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de38bb4323a6676a9185bee03e2ba490c9b9bc2be0fcb41e213849ef9755c9f2
|
|
4
|
+
data.tar.gz: 54e8bd60a653ecdf126480f2530b15cf52c024d9b69acd3152fd7ffa86f52c0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c319db67ae5621fd89a52d86e02022b73c4a2872763ec1cc48ef5a43c0f9b47b0ae3fa4927c1d180b60d0af83dfaa6085e788f8e66b91c3213c804a9e5fc56b6
|
|
7
|
+
data.tar.gz: 2d59d2fb65f0b2c05e189e11d25d20dcf9980fe9dc23d4c1f14c96050a7ef67bc7e162731b0fe5f6c0a717557ba9b8b12d98d4481197abc21a69b9d2a1c5b900
|
|
@@ -58,7 +58,7 @@ module ForestLiana
|
|
|
58
58
|
begin
|
|
59
59
|
# action.hooks[:change] is a hashmap here
|
|
60
60
|
# to do the validation, only the hook names are require
|
|
61
|
-
change_hooks_name = action.hooks[:change]
|
|
61
|
+
change_hooks_name = action.hooks && action.hooks[:change] ? action.hooks[:change].keys : nil
|
|
62
62
|
ForestLiana::SmartActionFieldValidator.validate_smart_action_fields(result[:fields], action.name, change_hooks_name)
|
|
63
63
|
rescue ForestLiana::Errors::SmartActionInvalidFieldError => invalid_field_error
|
|
64
64
|
FOREST_LOGGER.warn invalid_field_error.message
|
|
@@ -107,10 +107,15 @@ module ForestLiana
|
|
|
107
107
|
# Get the smart action hook load context
|
|
108
108
|
context = get_smart_action_load_ctx(action.fields)
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
if action.hooks && action.hooks[:load].present?
|
|
111
|
+
# Call the user-defined load hook.
|
|
112
|
+
result = action.hooks[:load].(context)
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
handle_result(result, action)
|
|
115
|
+
else
|
|
116
|
+
# No load hook declared: answer the static form (Node agent parity) instead of a 404.
|
|
117
|
+
handle_result(context[:fields], action)
|
|
118
|
+
end
|
|
114
119
|
end
|
|
115
120
|
end
|
|
116
121
|
|
|
@@ -266,6 +266,9 @@ module ForestLiana
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
collection.fields = collection.fields.reject do |field|
|
|
269
|
+
# NOTICE: A primary key column must survive; a type column is never one.
|
|
270
|
+
next false if field[:is_primary_key]
|
|
271
|
+
|
|
269
272
|
field[:field] == association.foreign_key || field[:field] == association.foreign_type
|
|
270
273
|
end
|
|
271
274
|
# NOTICE: Delete the association if the targeted model is excluded.
|
|
@@ -274,7 +277,8 @@ module ForestLiana
|
|
|
274
277
|
x[:field] == association.foreign_key
|
|
275
278
|
end
|
|
276
279
|
|
|
277
|
-
|
|
280
|
+
# NOTICE: A primary key column must survive, even as an excluded association's FK.
|
|
281
|
+
collection.fields.delete(field) if field && !field[:is_primary_key]
|
|
278
282
|
# NOTICE: The foreign key exists, so it's a belongsTo relationship.
|
|
279
283
|
elsif (field = column_association(collection, association)) &&
|
|
280
284
|
[:has_one, :belongs_to].include?(association.macro)
|
data/config/routes/actions.rb
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
ForestLiana.apimap.each do |collection|
|
|
2
2
|
if !collection.actions.empty?
|
|
3
3
|
collection.actions.each do |action|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
end
|
|
4
|
+
# Unconditional: clients probe /hooks/load even when no load hook is declared.
|
|
5
|
+
post action.endpoint.sub('forest', '') + '/hooks/load' => 'actions#load', action_name: ActiveSupport::Inflector.parameterize(action.name)
|
|
7
6
|
if action.hooks && action.hooks[:change].present?
|
|
8
7
|
post action.endpoint.sub('forest', '') + '/hooks/change' => 'actions#change', action_name: ActiveSupport::Inflector.parameterize(action.name)
|
|
9
8
|
end
|
data/lib/forest_liana/version.rb
CHANGED
|
@@ -28,6 +28,19 @@ class Forest::Island
|
|
|
28
28
|
|
|
29
29
|
action 'test'
|
|
30
30
|
|
|
31
|
+
action 'static_form_action',
|
|
32
|
+
fields: [{
|
|
33
|
+
field: 'static_field',
|
|
34
|
+
type: 'String',
|
|
35
|
+
default_value: nil,
|
|
36
|
+
enums: nil,
|
|
37
|
+
is_required: false,
|
|
38
|
+
is_read_only: false,
|
|
39
|
+
reference: nil,
|
|
40
|
+
description: nil,
|
|
41
|
+
widget: nil
|
|
42
|
+
}]
|
|
43
|
+
|
|
31
44
|
action 'my_action',
|
|
32
45
|
fields: [foo],
|
|
33
46
|
hooks: {
|
|
@@ -130,6 +130,25 @@ describe 'Requesting Actions routes', :type => :request do
|
|
|
130
130
|
expect(response.status).to eq(422)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
it 'should respond 200 with the static form when the action has no load hook' do
|
|
134
|
+
post '/forest/actions/static_form_action/hooks/load', params: JSON.dump(params), headers: headers
|
|
135
|
+
action = island.actions.select { |action| action.name == 'static_form_action' }.first
|
|
136
|
+
static_field = action.fields.select { |field| field[:field] == 'static_field' }.first
|
|
137
|
+
expect(response.status).to eq(200)
|
|
138
|
+
expect(JSON.parse(response.body)).to eq({'fields' => [static_field.merge({:value => nil}).transform_keys { |key| key.to_s.camelize(:lower) }.stringify_keys]})
|
|
139
|
+
|
|
140
|
+
first_body = response.body
|
|
141
|
+
post '/forest/actions/static_form_action/hooks/load', params: JSON.dump(params), headers: headers
|
|
142
|
+
expect(response.status).to eq(200)
|
|
143
|
+
expect(response.body).to eq(first_body)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'should respond 200 with empty fields when the action has no form at all' do
|
|
147
|
+
post '/forest/actions/test/hooks/load', params: JSON.dump(params), headers: headers
|
|
148
|
+
expect(response.status).to eq(200)
|
|
149
|
+
expect(JSON.parse(response.body)).to eq({'fields' => []})
|
|
150
|
+
end
|
|
151
|
+
|
|
133
152
|
it 'should respond 500 with bad hook result type' do
|
|
134
153
|
post '/forest/actions/fail_action/hooks/load', params: JSON.dump(params), headers: headers
|
|
135
154
|
expect(response.status).to eq(500)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
require 'rails_helper'
|
|
2
|
+
require 'zlib'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'socket'
|
|
2
5
|
|
|
3
6
|
describe 'Workflow executor proxy', type: :request do
|
|
4
7
|
let(:run_id) { 'run_abc123' }
|
|
@@ -207,6 +210,53 @@ describe 'Workflow executor proxy', type: :request do
|
|
|
207
210
|
end
|
|
208
211
|
end
|
|
209
212
|
|
|
213
|
+
# Real local server + real HTTParty (no stub) to exercise actual gzip decompression.
|
|
214
|
+
describe 'when the executor gzip-compresses the response (nginx in front)' do
|
|
215
|
+
let(:gz_body) do
|
|
216
|
+
io = StringIO.new
|
|
217
|
+
writer = Zlib::GzipWriter.new(io)
|
|
218
|
+
writer.write({ 'id' => run_id, 'state' => 'pending' }.to_json)
|
|
219
|
+
writer.close
|
|
220
|
+
io.string
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
around do |example|
|
|
224
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
225
|
+
port = server.addr[1]
|
|
226
|
+
thread = Thread.new do
|
|
227
|
+
client = server.accept
|
|
228
|
+
client.readpartial(4096)
|
|
229
|
+
client.write(
|
|
230
|
+
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n" \
|
|
231
|
+
"Content-Encoding: gzip\r\nContent-Length: #{gz_body.bytesize}\r\n" \
|
|
232
|
+
"Connection: close\r\n\r\n"
|
|
233
|
+
)
|
|
234
|
+
client.write(gz_body)
|
|
235
|
+
client.close
|
|
236
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
original = ForestLiana.workflow_executor_url
|
|
241
|
+
ForestLiana.workflow_executor_url = "http://127.0.0.1:#{port}"
|
|
242
|
+
example.run
|
|
243
|
+
ensure
|
|
244
|
+
ForestLiana.workflow_executor_url = original
|
|
245
|
+
thread&.kill
|
|
246
|
+
server&.close
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
before { allow(HTTParty).to receive(:get).and_call_original }
|
|
250
|
+
|
|
251
|
+
it 'decompresses the body and drops the stale content-encoding' do
|
|
252
|
+
get "/forest/_internal/executor/runs/#{run_id}", headers: auth_headers
|
|
253
|
+
|
|
254
|
+
expect(response.status).to eq(200)
|
|
255
|
+
expect(JSON.parse(response.body)).to eq('id' => run_id, 'state' => 'pending')
|
|
256
|
+
expect(response.headers['content-encoding']).to be_nil
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
210
260
|
describe 'when ForestLiana.workflow_executor_url is blank' do
|
|
211
261
|
around do |example|
|
|
212
262
|
original = ForestLiana.workflow_executor_url
|
|
@@ -86,6 +86,62 @@ module ForestLiana
|
|
|
86
86
|
ForestLiana.apimap << original if original
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
test 'a primary key column that is also the foreign key of an excluded association is kept' do
|
|
90
|
+
excluded_models = ForestLiana.excluded_models
|
|
91
|
+
name = ForestLiana.name_for(BelongsToField)
|
|
92
|
+
original = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
93
|
+
ForestLiana.apimap.delete(original) if original
|
|
94
|
+
ForestLiana.excluded_models = [ForestLiana.name_for(HasOneField)]
|
|
95
|
+
BelongsToField.define_singleton_method(:primary_key) { 'has_one_field_id' }
|
|
96
|
+
|
|
97
|
+
schema = SchemaAdapter.new(BelongsToField).perform
|
|
98
|
+
field = schema.fields.find { |f| f[:field] == 'has_one_field_id' }
|
|
99
|
+
|
|
100
|
+
refute_nil field, 'the primary key column must not be deleted'
|
|
101
|
+
assert_equal true, field[:is_primary_key]
|
|
102
|
+
ensure
|
|
103
|
+
BelongsToField.singleton_class.send(:remove_method, :primary_key)
|
|
104
|
+
ForestLiana.excluded_models = excluded_models
|
|
105
|
+
rebuilt = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
106
|
+
ForestLiana.apimap.delete(rebuilt) if rebuilt
|
|
107
|
+
ForestLiana.apimap << original if original
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
test 'an ordinary foreign key of an excluded association is still deleted' do
|
|
111
|
+
excluded_models = ForestLiana.excluded_models
|
|
112
|
+
name = ForestLiana.name_for(BelongsToField)
|
|
113
|
+
original = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
114
|
+
ForestLiana.apimap.delete(original) if original
|
|
115
|
+
ForestLiana.excluded_models = [ForestLiana.name_for(HasOneField)]
|
|
116
|
+
|
|
117
|
+
schema = SchemaAdapter.new(BelongsToField).perform
|
|
118
|
+
|
|
119
|
+
assert_nil schema.fields.find { |f| f[:field] == 'has_one_field_id' }
|
|
120
|
+
ensure
|
|
121
|
+
ForestLiana.excluded_models = excluded_models
|
|
122
|
+
rebuilt = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
123
|
+
ForestLiana.apimap.delete(rebuilt) if rebuilt
|
|
124
|
+
ForestLiana.apimap << original if original
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
test 'a primary key column that is also a polymorphic foreign key is kept' do
|
|
128
|
+
name = ForestLiana.name_for(PolymorphicField)
|
|
129
|
+
original = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
130
|
+
ForestLiana.apimap.delete(original) if original
|
|
131
|
+
PolymorphicField.define_singleton_method(:primary_key) { 'has_one_field_id' }
|
|
132
|
+
|
|
133
|
+
schema = SchemaAdapter.new(PolymorphicField).perform
|
|
134
|
+
field = schema.fields.find { |f| f[:field] == 'has_one_field_id' }
|
|
135
|
+
|
|
136
|
+
refute_nil field, 'the primary key column must not be rejected'
|
|
137
|
+
assert_equal true, field[:is_primary_key]
|
|
138
|
+
ensure
|
|
139
|
+
PolymorphicField.singleton_class.send(:remove_method, :primary_key)
|
|
140
|
+
rebuilt = ForestLiana.apimap.find { |c| c.name.to_s == name }
|
|
141
|
+
ForestLiana.apimap.delete(rebuilt) if rebuilt
|
|
142
|
+
ForestLiana.apimap << original if original
|
|
143
|
+
end
|
|
144
|
+
|
|
89
145
|
test 'belongsTo relationship' do
|
|
90
146
|
schema = SchemaAdapter.new(BelongsToField).perform
|
|
91
147
|
fields = schema.fields.select do |field|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_liana
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.20.
|
|
4
|
+
version: 9.20.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sandro Munda
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|