cmdx 1.1.1 → 1.5.0
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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/.ruby-version +1 -1
- data/CHANGELOG.md +6 -128
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -60
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -52
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
@@ -1,104 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CMDx
|
4
|
-
# Result serialization module for converting result objects to hash format.
|
5
|
-
#
|
6
|
-
# This module provides functionality to serialize result objects into a
|
7
|
-
# standardized hash representation that includes essential metadata about
|
8
|
-
# the result such as task information, execution state, status, outcome,
|
9
|
-
# metadata, and runtime. For failed results, it intelligently strips
|
10
|
-
# redundant failure information to avoid duplication in serialized output.
|
11
|
-
module ResultSerializer
|
12
|
-
|
13
|
-
# Proc for stripping failure information from serialized results.
|
14
|
-
# Removes caused_failure and threw_failure keys when the result doesn't
|
15
|
-
# have the corresponding failure state, avoiding redundant information.
|
16
|
-
STRIP_FAILURE = proc do |h, r, k|
|
17
|
-
unless r.send(:"#{k}?")
|
18
|
-
# Strip caused/threw failures since its the same info as the log line
|
19
|
-
h[k] = r.send(k).to_h.except(:caused_failure, :threw_failure)
|
20
|
-
end
|
21
|
-
end.freeze
|
22
|
-
|
23
|
-
module_function
|
24
|
-
|
25
|
-
# Serializes a result object into a hash representation.
|
26
|
-
#
|
27
|
-
# Converts a result instance into a standardized hash format containing
|
28
|
-
# task metadata and execution information. For failed results, applies
|
29
|
-
# intelligent failure stripping to remove redundant caused_failure and
|
30
|
-
# threw_failure information that would duplicate log output.
|
31
|
-
#
|
32
|
-
# @param result [CMDx::Result] the result object to serialize
|
33
|
-
#
|
34
|
-
# @return [Hash] a hash containing the result's metadata and execution information
|
35
|
-
# @option return [Integer] :index the result's position index in the execution chain
|
36
|
-
# @option return [String] :chain_id the unique identifier of the result's execution chain
|
37
|
-
# @option return [String] :type the task type, either "Task" or "Workflow"
|
38
|
-
# @option return [String] :class the full class name of the task
|
39
|
-
# @option return [String] :id the unique identifier of the task instance
|
40
|
-
# @option return [Array] :tags the tags associated with the task from cmd settings
|
41
|
-
# @option return [Symbol] :state the execution state (:executing, :complete, :interrupted)
|
42
|
-
# @option return [Symbol] :status the execution status (:success, :failed, :skipped)
|
43
|
-
# @option return [Symbol] :outcome the execution outcome (:good, :bad)
|
44
|
-
# @option return [Hash] :metadata additional metadata collected during execution
|
45
|
-
# @option return [Float] :runtime the execution runtime in seconds
|
46
|
-
# @option return [Hash] :caused_failure failure information if result caused a failure (stripped for non-failed results)
|
47
|
-
# @option return [Hash] :threw_failure failure information if result threw a failure (stripped for non-failed results)
|
48
|
-
#
|
49
|
-
# @raise [NoMethodError] if the result doesn't respond to required methods
|
50
|
-
#
|
51
|
-
# @example Serialize a successful result
|
52
|
-
# task = SuccessfulTask.new(data: "test")
|
53
|
-
# ResultSerializer.call(result)
|
54
|
-
# #=> {
|
55
|
-
# # index: 0,
|
56
|
-
# # chain_id: "abc123",
|
57
|
-
# # type: "Task",
|
58
|
-
# # class: "SuccessfulTask",
|
59
|
-
# # id: "def456",
|
60
|
-
# # tags: [],
|
61
|
-
# # state: :complete,
|
62
|
-
# # status: :success,
|
63
|
-
# # outcome: :good,
|
64
|
-
# # metadata: {},
|
65
|
-
# # runtime: 0.045
|
66
|
-
# # }
|
67
|
-
#
|
68
|
-
# @example Serialize a failed result with failure stripping
|
69
|
-
# task = FailingTask.call
|
70
|
-
# ResultSerializer.call(task.result)
|
71
|
-
# #=> {
|
72
|
-
# # index: 1,
|
73
|
-
# # chain_id: "xyz789",
|
74
|
-
# # type: "Task",
|
75
|
-
# # class: "FailingTask",
|
76
|
-
# # id: "ghi012",
|
77
|
-
# # tags: [],
|
78
|
-
# # state: :interrupted,
|
79
|
-
# # status: :failed,
|
80
|
-
# # outcome: :bad,
|
81
|
-
# # metadata: { reason: "Database connection failed" },
|
82
|
-
# # runtime: 0.012,
|
83
|
-
# # caused_failure: { message: "Task failed", ... },
|
84
|
-
# # threw_failure: { message: "Validation error", ... },
|
85
|
-
# # }
|
86
|
-
def call(result)
|
87
|
-
TaskSerializer.call(result.task).tap do |hash|
|
88
|
-
hash.merge!(
|
89
|
-
state: result.state,
|
90
|
-
status: result.status,
|
91
|
-
outcome: result.outcome,
|
92
|
-
metadata: result.metadata,
|
93
|
-
runtime: result.runtime
|
94
|
-
)
|
95
|
-
|
96
|
-
if result.failed?
|
97
|
-
STRIP_FAILURE.call(hash, result, :caused_failure)
|
98
|
-
STRIP_FAILURE.call(hash, result, :threw_failure)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
data/lib/cmdx/rspec/matchers.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Result matchers
|
4
|
-
require_relative "result_matchers/be_successful_task"
|
5
|
-
require_relative "result_matchers/be_failed_task"
|
6
|
-
require_relative "result_matchers/be_skipped_task"
|
7
|
-
require_relative "result_matchers/be_executed"
|
8
|
-
require_relative "result_matchers/be_state_matchers"
|
9
|
-
require_relative "result_matchers/be_status_matchers"
|
10
|
-
require_relative "result_matchers/have_good_outcome"
|
11
|
-
require_relative "result_matchers/have_bad_outcome"
|
12
|
-
require_relative "result_matchers/have_runtime"
|
13
|
-
require_relative "result_matchers/have_metadata"
|
14
|
-
require_relative "result_matchers/have_empty_metadata"
|
15
|
-
require_relative "result_matchers/have_context"
|
16
|
-
require_relative "result_matchers/have_preserved_context"
|
17
|
-
require_relative "result_matchers/have_caused_failure"
|
18
|
-
require_relative "result_matchers/have_thrown_failure"
|
19
|
-
require_relative "result_matchers/have_received_thrown_failure"
|
20
|
-
require_relative "result_matchers/have_chain_index"
|
21
|
-
|
22
|
-
# Task matchers
|
23
|
-
require_relative "task_matchers/be_well_formed_task"
|
24
|
-
require_relative "task_matchers/have_cmd_setting"
|
25
|
-
require_relative "task_matchers/have_middleware"
|
26
|
-
require_relative "task_matchers/have_callback"
|
27
|
-
require_relative "task_matchers/have_parameter"
|
28
|
-
require_relative "task_matchers/have_executed_callbacks"
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has been executed.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object is in an executed state,
|
6
|
-
# which occurs when the task has finished execution regardless of whether
|
7
|
-
# it succeeded, failed, or was skipped. A result is considered executed
|
8
|
-
# when it's in either "complete" or "interrupted" state.
|
9
|
-
#
|
10
|
-
# @return [Boolean] true if the result is executed (complete or interrupted)
|
11
|
-
#
|
12
|
-
# @example Basic usage with successful task
|
13
|
-
# result = MyTask.call(user_id: 123)
|
14
|
-
# expect(result).to be_executed
|
15
|
-
#
|
16
|
-
# @example Usage with failed task
|
17
|
-
# result = FailingTask.call
|
18
|
-
# expect(result).to be_executed
|
19
|
-
#
|
20
|
-
# @example Negative assertion
|
21
|
-
# task = MyTask.new
|
22
|
-
# expect(task.result).not_to be_executed
|
23
|
-
#
|
24
|
-
# @example In workflow integration tests
|
25
|
-
# result = MyWorkflow.call(data: "test")
|
26
|
-
# expect(result).to be_executed
|
27
|
-
# expect(result.context.processed).to be(true)
|
28
|
-
RSpec::Matchers.define :be_executed do
|
29
|
-
match(&:executed?)
|
30
|
-
|
31
|
-
failure_message do |result|
|
32
|
-
"expected result to be executed, but was in #{result.state} state"
|
33
|
-
end
|
34
|
-
|
35
|
-
failure_message_when_negated do |result|
|
36
|
-
"expected result not to be executed, but it was (state: #{result.state})"
|
37
|
-
end
|
38
|
-
|
39
|
-
description do
|
40
|
-
"be executed"
|
41
|
-
end
|
42
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has failed with specific conditions.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object is in a failed state, which means
|
6
|
-
# the task was executed but encountered an error or failure condition. A result
|
7
|
-
# is considered failed when it's in both "failed" status and "interrupted" state,
|
8
|
-
# and has been executed. Optionally checks for specific failure reasons and metadata.
|
9
|
-
#
|
10
|
-
# @param expected_reason [String, Symbol, nil] optional expected failure reason
|
11
|
-
#
|
12
|
-
# @return [Boolean] true if the result is failed, interrupted, executed, and matches expected criteria
|
13
|
-
#
|
14
|
-
# @example Basic usage with failed task
|
15
|
-
# result = ValidateUserTask.call(user_id: nil)
|
16
|
-
# expect(result).to be_failed_task
|
17
|
-
#
|
18
|
-
# @example Checking for specific failure reason
|
19
|
-
# result = ProcessPaymentTask.call(amount: -100)
|
20
|
-
# expect(result).to be_failed_task("invalid_amount")
|
21
|
-
#
|
22
|
-
# @example Using with_reason chain
|
23
|
-
# result = AuthenticateUserTask.call(token: "invalid")
|
24
|
-
# expect(result).to be_failed_task.with_reason(:authentication_failed)
|
25
|
-
#
|
26
|
-
# @example Checking failure with metadata
|
27
|
-
# result = UploadFileTask.call(file: corrupted_file)
|
28
|
-
# expect(result).to be_failed_task.with_metadata(file_size: 0, error_code: "CORRUPTED")
|
29
|
-
#
|
30
|
-
# @example Combining reason and metadata checks
|
31
|
-
# result = ValidateDataTask.call(data: invalid_data)
|
32
|
-
# expect(result).to be_failed_task("validation_error").with_metadata(field: "email", rule: "format")
|
33
|
-
#
|
34
|
-
# @example Negative assertion
|
35
|
-
# result = SuccessfulTask.call(data: "valid")
|
36
|
-
# expect(result).not_to be_failed_task
|
37
|
-
RSpec::Matchers.define :be_failed_task do |expected_reason = nil|
|
38
|
-
match do |result|
|
39
|
-
result.failed? &&
|
40
|
-
result.interrupted? &&
|
41
|
-
result.executed? &&
|
42
|
-
(expected_reason.nil? || result.metadata[:reason] == expected_reason)
|
43
|
-
end
|
44
|
-
|
45
|
-
chain :with_reason do |reason|
|
46
|
-
@expected_reason = reason
|
47
|
-
end
|
48
|
-
|
49
|
-
chain :with_metadata do |metadata|
|
50
|
-
@expected_metadata = metadata
|
51
|
-
end
|
52
|
-
|
53
|
-
match do |result|
|
54
|
-
reason = @expected_reason || expected_reason
|
55
|
-
metadata = @expected_metadata || {}
|
56
|
-
|
57
|
-
result.failed? &&
|
58
|
-
result.interrupted? &&
|
59
|
-
result.executed? &&
|
60
|
-
(reason.nil? || result.metadata[:reason] == reason) &&
|
61
|
-
(metadata.empty? || metadata.all? { |k, v| result.metadata[k] == v })
|
62
|
-
end
|
63
|
-
|
64
|
-
failure_message do |result|
|
65
|
-
messages = []
|
66
|
-
messages << "expected result to be failed, but was #{result.status}" unless result.failed?
|
67
|
-
messages << "expected result to be interrupted, but was #{result.state}" unless result.interrupted?
|
68
|
-
messages << "expected result to be executed, but was not" unless result.executed?
|
69
|
-
|
70
|
-
reason = @expected_reason || expected_reason
|
71
|
-
messages << "expected failure reason to be '#{reason}', but was '#{result.metadata[:reason]}'" if reason && result.metadata[:reason] != reason
|
72
|
-
|
73
|
-
if @expected_metadata&.any?
|
74
|
-
mismatches = @expected_metadata.filter_map do |k, v|
|
75
|
-
"#{k}: expected #{v}, got #{result.metadata[k]}" if result.metadata[k] != v
|
76
|
-
end
|
77
|
-
messages.concat(mismatches)
|
78
|
-
end
|
79
|
-
|
80
|
-
messages.join(", ")
|
81
|
-
end
|
82
|
-
|
83
|
-
failure_message_when_negated do |_result|
|
84
|
-
"expected result not to be failed, but it was"
|
85
|
-
end
|
86
|
-
|
87
|
-
description do
|
88
|
-
desc = "be a failed task"
|
89
|
-
reason = @expected_reason || expected_reason
|
90
|
-
desc += " with reason '#{reason}'" if reason
|
91
|
-
desc += " with metadata #{@expected_metadata}" if @expected_metadata&.any?
|
92
|
-
desc
|
93
|
-
end
|
94
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has been skipped with specific conditions.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object is in a skipped state, which means
|
6
|
-
# the task was executed but was intentionally skipped due to some condition. A result
|
7
|
-
# is considered skipped when it's in both "skipped" status and "interrupted" state,
|
8
|
-
# and has been executed. Optionally checks for specific skip reasons and metadata.
|
9
|
-
#
|
10
|
-
# @param expected_reason [String, Symbol, nil] optional expected skip reason
|
11
|
-
#
|
12
|
-
# @return [Boolean] true if the result is skipped, interrupted, executed, and matches expected criteria
|
13
|
-
#
|
14
|
-
# @example Basic usage with skipped task
|
15
|
-
# result = ProcessUserTask.call(user_id: 123)
|
16
|
-
# expect(result).to be_skipped_task
|
17
|
-
#
|
18
|
-
# @example Checking for specific skip reason
|
19
|
-
# result = SendEmailTask.call(user: inactive_user)
|
20
|
-
# expect(result).to be_skipped_task("user_inactive")
|
21
|
-
#
|
22
|
-
# @example Using with_reason chain
|
23
|
-
# result = BackupDataTask.call(force: false)
|
24
|
-
# expect(result).to be_skipped_task.with_reason(:backup_not_needed)
|
25
|
-
#
|
26
|
-
# @example Checking skip with metadata
|
27
|
-
# result = ProcessQueueTask.call(queue: empty_queue)
|
28
|
-
# expect(result).to be_skipped_task.with_metadata(queue_size: 0, processed_count: 0)
|
29
|
-
#
|
30
|
-
# @example Combining reason and metadata checks
|
31
|
-
# result = SyncDataTask.call(data: outdated_data)
|
32
|
-
# expect(result).to be_skipped_task("data_unchanged").with_metadata(last_sync: timestamp, changes: 0)
|
33
|
-
#
|
34
|
-
# @example Negative assertion
|
35
|
-
# result = ExecutedTask.call(data: "valid")
|
36
|
-
# expect(result).not_to be_skipped_task
|
37
|
-
RSpec::Matchers.define :be_skipped_task do |expected_reason = nil|
|
38
|
-
match do |result|
|
39
|
-
result.skipped? &&
|
40
|
-
result.interrupted? &&
|
41
|
-
result.executed? &&
|
42
|
-
(expected_reason.nil? || result.metadata[:reason] == expected_reason)
|
43
|
-
end
|
44
|
-
|
45
|
-
chain :with_reason do |reason|
|
46
|
-
@expected_reason = reason
|
47
|
-
end
|
48
|
-
|
49
|
-
chain :with_metadata do |metadata|
|
50
|
-
@expected_metadata = metadata
|
51
|
-
end
|
52
|
-
|
53
|
-
match do |result|
|
54
|
-
reason = @expected_reason || expected_reason
|
55
|
-
metadata = @expected_metadata || {}
|
56
|
-
|
57
|
-
result.skipped? &&
|
58
|
-
result.interrupted? &&
|
59
|
-
result.executed? &&
|
60
|
-
(reason.nil? || result.metadata[:reason] == reason) &&
|
61
|
-
(metadata.empty? || metadata.all? { |k, v| result.metadata[k] == v })
|
62
|
-
end
|
63
|
-
|
64
|
-
failure_message do |result|
|
65
|
-
messages = []
|
66
|
-
messages << "expected result to be skipped, but was #{result.status}" unless result.skipped?
|
67
|
-
messages << "expected result to be interrupted, but was #{result.state}" unless result.interrupted?
|
68
|
-
messages << "expected result to be executed, but was not" unless result.executed?
|
69
|
-
|
70
|
-
reason = @expected_reason || expected_reason
|
71
|
-
messages << "expected skip reason to be '#{reason}', but was '#{result.metadata[:reason]}'" if reason && result.metadata[:reason] != reason
|
72
|
-
|
73
|
-
if @expected_metadata&.any?
|
74
|
-
mismatches = @expected_metadata.filter_map do |k, v|
|
75
|
-
"#{k}: expected #{v}, got #{result.metadata[k]}" if result.metadata[k] != v
|
76
|
-
end
|
77
|
-
messages.concat(mismatches)
|
78
|
-
end
|
79
|
-
|
80
|
-
messages.join(", ")
|
81
|
-
end
|
82
|
-
|
83
|
-
failure_message_when_negated do |_result|
|
84
|
-
"expected result not to be skipped, but it was"
|
85
|
-
end
|
86
|
-
|
87
|
-
description do
|
88
|
-
desc = "be a skipped task"
|
89
|
-
reason = @expected_reason || expected_reason
|
90
|
-
desc += " with reason '#{reason}'" if reason
|
91
|
-
desc += " with metadata #{@expected_metadata}" if @expected_metadata&.any?
|
92
|
-
desc
|
93
|
-
end
|
94
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matchers for asserting task result states.
|
4
|
-
#
|
5
|
-
# This file dynamically generates RSpec matchers for each execution state defined
|
6
|
-
# in CMDx::Result::STATES. These matchers check the current execution state of a
|
7
|
-
# task result, which represents where the task is in its lifecycle from
|
8
|
-
# initialization through completion or interruption.
|
9
|
-
#
|
10
|
-
# The following matchers are automatically generated:
|
11
|
-
# - `be_initialized` - Task has been created but not yet started
|
12
|
-
# - `be_executing` - Task is currently running its logic
|
13
|
-
# - `be_complete` - Task has successfully finished execution
|
14
|
-
# - `be_interrupted` - Task execution was halted due to failure or skip
|
15
|
-
#
|
16
|
-
# @return [Boolean] true if the result matches the expected state
|
17
|
-
#
|
18
|
-
# @example Testing initialized state
|
19
|
-
# result = MyTask.new.result
|
20
|
-
# expect(result).to be_initialized
|
21
|
-
#
|
22
|
-
# @example Testing executing state
|
23
|
-
# result = MyTask.call(data: "processing")
|
24
|
-
# expect(result).to be_executing # During execution
|
25
|
-
#
|
26
|
-
# @example Testing complete state
|
27
|
-
# result = SuccessfulTask.call(data: "valid")
|
28
|
-
# expect(result).to be_complete
|
29
|
-
#
|
30
|
-
# @example Testing interrupted state
|
31
|
-
# result = FailedTask.call(data: "invalid")
|
32
|
-
# expect(result).to be_interrupted
|
33
|
-
#
|
34
|
-
# @example Negative assertion
|
35
|
-
# result = SuccessfulTask.call(data: "valid")
|
36
|
-
# expect(result).not_to be_initialized
|
37
|
-
#
|
38
|
-
# @example Using with other matchers
|
39
|
-
# result = ProcessDataTask.call(data: invalid_data)
|
40
|
-
# expect(result).to be_interrupted.and be_failed
|
41
|
-
CMDx::Result::STATES.each do |state|
|
42
|
-
RSpec::Matchers.define :"be_#{state}" do
|
43
|
-
match do |result|
|
44
|
-
result.public_send(:"#{state}?")
|
45
|
-
end
|
46
|
-
|
47
|
-
failure_message do |result|
|
48
|
-
"expected result to be #{state}, but was #{result.state}"
|
49
|
-
end
|
50
|
-
|
51
|
-
failure_message_when_negated do |_result|
|
52
|
-
"expected result not to be #{state}, but it was"
|
53
|
-
end
|
54
|
-
|
55
|
-
description do
|
56
|
-
"be #{state}"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matchers for asserting task result statuses.
|
4
|
-
#
|
5
|
-
# This file dynamically generates RSpec matchers for each execution status defined
|
6
|
-
# in CMDx::Result::STATUSES. These matchers check the outcome of task logic execution,
|
7
|
-
# which represents what happened when the task's business logic ran (success, skip, or failure).
|
8
|
-
#
|
9
|
-
# The following matchers are automatically generated:
|
10
|
-
# - `be_success` - Task completed successfully without errors
|
11
|
-
# - `be_skipped` - Task was intentionally skipped due to conditions
|
12
|
-
# - `be_failed` - Task failed due to errors or validation issues
|
13
|
-
#
|
14
|
-
# @return [Boolean] true if the result matches the expected status
|
15
|
-
#
|
16
|
-
# @example Testing success status
|
17
|
-
# result = ProcessDataTask.call(data: "valid")
|
18
|
-
# expect(result).to be_success
|
19
|
-
#
|
20
|
-
# @example Testing skipped status
|
21
|
-
# result = SendEmailTask.call(user: inactive_user)
|
22
|
-
# expect(result).to be_skipped
|
23
|
-
#
|
24
|
-
# @example Testing failed status
|
25
|
-
# result = ValidateUserTask.call(user_id: nil)
|
26
|
-
# expect(result).to be_failed
|
27
|
-
#
|
28
|
-
# @example Negative assertion
|
29
|
-
# result = SuccessfulTask.call(data: "valid")
|
30
|
-
# expect(result).not_to be_failed
|
31
|
-
#
|
32
|
-
# @example Using with state matchers
|
33
|
-
# result = ProcessPaymentTask.call(amount: -100)
|
34
|
-
# expect(result).to be_failed.and be_interrupted
|
35
|
-
#
|
36
|
-
# @example Testing good vs bad outcomes
|
37
|
-
# result = BackupTask.call(force: false)
|
38
|
-
# expect(result).to be_skipped # Skipped is still a "good" outcome
|
39
|
-
CMDx::Result::STATUSES.each do |status|
|
40
|
-
RSpec::Matchers.define :"be_#{status}" do
|
41
|
-
match do |result|
|
42
|
-
result.public_send(:"#{status}?")
|
43
|
-
end
|
44
|
-
|
45
|
-
failure_message do |result|
|
46
|
-
"expected result to be #{status}, but was #{result.status}"
|
47
|
-
end
|
48
|
-
|
49
|
-
failure_message_when_negated do |_result|
|
50
|
-
"expected result not to be #{status}, but it was"
|
51
|
-
end
|
52
|
-
|
53
|
-
description do
|
54
|
-
"be #{status}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has completed successfully.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object represents a fully successful task
|
6
|
-
# execution, which means the task completed without errors and reached the end of
|
7
|
-
# its lifecycle. A result is considered a successful task when it has "success" status,
|
8
|
-
# "complete" state, and has been executed. Optionally validates expected context values.
|
9
|
-
#
|
10
|
-
# @param expected_context [Hash] optional hash of expected context key-value pairs
|
11
|
-
#
|
12
|
-
# @return [Boolean] true if the result is successful, complete, executed, and matches expected context
|
13
|
-
#
|
14
|
-
# @example Basic usage with successful task
|
15
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
16
|
-
# expect(result).to be_successful_task
|
17
|
-
#
|
18
|
-
# @example Checking successful task with context validation
|
19
|
-
# result = CalculateTotalTask.call(items: [item1, item2])
|
20
|
-
# expect(result).to be_successful_task(total: 150.00, tax: 12.50)
|
21
|
-
#
|
22
|
-
# @example Validating multiple context attributes
|
23
|
-
# result = UserRegistrationTask.call(email: "user@example.com")
|
24
|
-
# expect(result).to be_successful_task(
|
25
|
-
# user_id: 42,
|
26
|
-
# email_sent: true,
|
27
|
-
# activation_token: be_present
|
28
|
-
# )
|
29
|
-
#
|
30
|
-
# @example Negative assertion
|
31
|
-
# result = FailedValidationTask.call(data: "invalid")
|
32
|
-
# expect(result).not_to be_successful_task
|
33
|
-
#
|
34
|
-
# @example Combining with other matchers
|
35
|
-
# result = ProcessPaymentTask.call(amount: 100)
|
36
|
-
# expect(result).to be_successful_task.and have_runtime
|
37
|
-
#
|
38
|
-
# @example Testing context without specific values
|
39
|
-
# result = DataProcessingTask.call(data: dataset)
|
40
|
-
# expect(result).to be_successful_task({}) # Just check success without context
|
41
|
-
RSpec::Matchers.define :be_successful_task do |expected_context = {}|
|
42
|
-
match do |result|
|
43
|
-
result.success? &&
|
44
|
-
result.complete? &&
|
45
|
-
result.executed? &&
|
46
|
-
(expected_context.empty? || context_matches?(result, expected_context))
|
47
|
-
end
|
48
|
-
|
49
|
-
failure_message do |result|
|
50
|
-
messages = []
|
51
|
-
messages << "expected result to be successful, but was #{result.status}" unless result.success?
|
52
|
-
messages << "expected result to be complete, but was #{result.state}" unless result.complete?
|
53
|
-
messages << "expected result to be executed, but was not" unless result.executed?
|
54
|
-
|
55
|
-
unless expected_context.empty?
|
56
|
-
mismatches = context_mismatches(result, expected_context)
|
57
|
-
messages << "expected context to match #{expected_context}, but #{mismatches}" if mismatches.any?
|
58
|
-
end
|
59
|
-
|
60
|
-
messages.join(", ")
|
61
|
-
end
|
62
|
-
|
63
|
-
failure_message_when_negated do |_result|
|
64
|
-
"expected result not to be successful, but it was"
|
65
|
-
end
|
66
|
-
|
67
|
-
description do
|
68
|
-
desc = "be a successful task"
|
69
|
-
desc += " with context #{expected_context}" unless expected_context.empty?
|
70
|
-
desc
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def context_matches?(result, expected_context)
|
76
|
-
expected_context.all? do |key, value|
|
77
|
-
result.context.public_send(key) == value
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def context_mismatches(result, expected_context)
|
82
|
-
expected_context.filter_map do |key, expected_value|
|
83
|
-
actual_value = result.context.public_send(key)
|
84
|
-
"#{key}: expected #{expected_value}, got #{actual_value}" if actual_value != expected_value
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has a bad outcome.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object represents a non-successful outcome,
|
6
|
-
# which includes both failed and skipped results. A result has a bad outcome when
|
7
|
-
# its status is anything other than "success" (i.e., either "failed" or "skipped").
|
8
|
-
# This is useful for testing error handling and conditional logic paths.
|
9
|
-
#
|
10
|
-
# @return [Boolean] true if the result has a bad outcome (failed or skipped)
|
11
|
-
#
|
12
|
-
# @example Testing failed task outcome
|
13
|
-
# result = ValidateDataTask.call(data: "invalid")
|
14
|
-
# expect(result).to have_bad_outcome
|
15
|
-
#
|
16
|
-
# @example Testing skipped task outcome
|
17
|
-
# result = ProcessQueueTask.call(queue: empty_queue)
|
18
|
-
# expect(result).to have_bad_outcome
|
19
|
-
#
|
20
|
-
# @example Testing error handling paths
|
21
|
-
# result = ProcessPaymentTask.call(amount: -100)
|
22
|
-
# expect(result).to have_bad_outcome.and be_failed
|
23
|
-
#
|
24
|
-
# @example Negative assertion for successful tasks
|
25
|
-
# result = SuccessfulTask.call(data: "valid")
|
26
|
-
# expect(result).not_to have_bad_outcome
|
27
|
-
#
|
28
|
-
# @example Using in conditional test logic
|
29
|
-
# result = ConditionalTask.call(condition: false)
|
30
|
-
# if result.bad?
|
31
|
-
# expect(result).to have_bad_outcome
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# @example Opposite of good outcome
|
35
|
-
# result = SkippedTask.call(reason: "not_needed")
|
36
|
-
# expect(result).to have_bad_outcome.and not_to have_good_outcome
|
37
|
-
RSpec::Matchers.define :have_bad_outcome do
|
38
|
-
match(&:bad?)
|
39
|
-
|
40
|
-
failure_message do |result|
|
41
|
-
"expected result to have bad outcome (not success), but was #{result.status}"
|
42
|
-
end
|
43
|
-
|
44
|
-
failure_message_when_negated do |result|
|
45
|
-
"expected result not to have bad outcome, but it did (status: #{result.status})"
|
46
|
-
end
|
47
|
-
|
48
|
-
description do
|
49
|
-
"have bad outcome"
|
50
|
-
end
|
51
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# RSpec matcher for asserting that a task result has caused its own failure.
|
4
|
-
#
|
5
|
-
# This matcher checks if a CMDx::Result object represents a failure that originated
|
6
|
-
# within the task itself, as opposed to a failure that was thrown from or received
|
7
|
-
# from another task. A result is considered to have caused failure when it's both
|
8
|
-
# failed and the failure was generated by the task's own logic rather than propagated
|
9
|
-
# from elsewhere in the chain.
|
10
|
-
#
|
11
|
-
# @return [Boolean] true if the result is failed and caused the failure itself
|
12
|
-
#
|
13
|
-
# @example Testing task that fails due to validation
|
14
|
-
# result = ValidateUserTask.call(email: "invalid-email")
|
15
|
-
# expect(result).to have_caused_failure
|
16
|
-
#
|
17
|
-
# @example Testing task that fails due to business logic
|
18
|
-
# result = ProcessPaymentTask.call(amount: -100)
|
19
|
-
# expect(result).to have_caused_failure
|
20
|
-
#
|
21
|
-
# @example Distinguishing from thrown failures
|
22
|
-
# result = TaskThatThrowsFailure.call(data: "invalid")
|
23
|
-
# expect(result).to have_caused_failure # This task caused its own failure
|
24
|
-
# expect(result).not_to have_thrown_failure
|
25
|
-
#
|
26
|
-
# @example Testing in workflow context
|
27
|
-
# workflow_result = MyWorkflow.call(data: "invalid")
|
28
|
-
# failing_task = workflow_result.chain.find(&:failed?)
|
29
|
-
# expect(failing_task).to have_caused_failure
|
30
|
-
#
|
31
|
-
# @example Negative assertion
|
32
|
-
# result = SuccessfulTask.call(data: "valid")
|
33
|
-
# expect(result).not_to have_caused_failure
|
34
|
-
#
|
35
|
-
# @example Testing error handling origin
|
36
|
-
# result = DatabaseTask.call(connection: nil)
|
37
|
-
# expect(result).to have_caused_failure.and be_failed
|
38
|
-
RSpec::Matchers.define :have_caused_failure do
|
39
|
-
match do |result|
|
40
|
-
result.failed? && result.caused_failure?
|
41
|
-
end
|
42
|
-
|
43
|
-
failure_message do |result|
|
44
|
-
if result.failed?
|
45
|
-
"expected result to have caused failure, but it threw/received a failure instead"
|
46
|
-
else
|
47
|
-
"expected result to have caused failure, but it was not failed (status: #{result.status})"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
failure_message_when_negated do |_result|
|
52
|
-
"expected result not to have caused failure, but it did"
|
53
|
-
end
|
54
|
-
|
55
|
-
description do
|
56
|
-
"have caused failure"
|
57
|
-
end
|
58
|
-
end
|