modspec 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 925062f2457c7aa978de3d2eabc679f4a33b9177e8ba2f3a80494c46377ff0b8
4
- data.tar.gz: '083c357ffd578b297ae28753e48dccacd08a10d202f336299b037d1d6b08fe9a'
3
+ metadata.gz: 74bb2806f9ebad2c91453ba473103f5e581c76b78223d9eeba79f4580e8858db
4
+ data.tar.gz: cb2903e0b33726419f118c65f64ad5463a2bef0b385643f877de977bc447f8d9
5
5
  SHA512:
6
- metadata.gz: ce9246f0f6b4ce811bf498d52a59c25949ad0016b024ab215c332ce7e1e34fa311c2a78267a3a1b07cb1a2f0f468559c1aeaedb638c4552e9500016ad8a3235b
7
- data.tar.gz: 7132a3d0194c87222d077752784d1d1d7197481d1db1ae356c9f959d8cccd8163f3ef268a2e4e6b75812095768e133ab6da81c629fd3d33b0122f7f2789079fe
6
+ metadata.gz: d31ab6413761a379a95aa692e94471538daa63cb507ecfcf25ab6e4b3bc34cdfc22d8234ca4239e0159921bdd203ce4e8bec8bec3e4d99323222c34d49d59ac0
7
+ data.tar.gz: 615225d2adf7b489e0d627147b3a3dbdd43009ed0c96df174f690b9ebeec6c7c752eb5a684505f6822af40d7ee9710ef2a165e9aa601ba6144d82b52a2192a38
data/.rubocop.yml CHANGED
@@ -17,3 +17,6 @@ plugins:
17
17
 
18
18
  AllCops:
19
19
  TargetRubyVersion: 3.0
20
+
21
+ RSpec/ExampleLength:
22
+ Max: 30
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-05 14:09:35 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-06 00:19:18 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,7 +11,48 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'modspec.gemspec'
13
13
 
14
- # Offense count: 33
14
+ # Offense count: 7
15
+ # This cop supports safe autocorrection (--autocorrect).
16
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
17
+ # SupportedStyles: with_first_argument, with_fixed_indentation
18
+ Layout/ArgumentAlignment:
19
+ Exclude:
20
+ - 'lib/modspec/suite.rb'
21
+ - 'spec/modspec/suite_spec.rb'
22
+
23
+ # Offense count: 1
24
+ # This cop supports safe autocorrection (--autocorrect).
25
+ # Configuration parameters: EnforcedStyleAlignWith.
26
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
27
+ Layout/BlockAlignment:
28
+ Exclude:
29
+ - 'lib/modspec/suite.rb'
30
+
31
+ # Offense count: 1
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ Layout/BlockEndNewline:
34
+ Exclude:
35
+ - 'lib/modspec/suite.rb'
36
+
37
+ # Offense count: 6
38
+ # This cop supports safe autocorrection (--autocorrect).
39
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
40
+ # SupportedHashRocketStyles: key, separator, table
41
+ # SupportedColonStyles: key, separator, table
42
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
43
+ Layout/HashAlignment:
44
+ Exclude:
45
+ - 'spec/modspec/suite_spec.rb'
46
+
47
+ # Offense count: 2
48
+ # This cop supports safe autocorrection (--autocorrect).
49
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
50
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
51
+ Layout/IndentationWidth:
52
+ Exclude:
53
+ - 'lib/modspec/suite.rb'
54
+
55
+ # Offense count: 36
15
56
  # This cop supports safe autocorrection (--autocorrect).
16
57
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
58
  # URISchemes: http, https
@@ -19,7 +60,6 @@ Layout/LineLength:
19
60
  Exclude:
20
61
  - 'lib/modspec/conformance_class.rb'
21
62
  - 'lib/modspec/conformance_test.rb'
22
- - 'lib/modspec/normative_statement.rb'
23
63
  - 'lib/modspec/normative_statements_class.rb'
24
64
  - 'lib/modspec/suite.rb'
25
65
  - 'spec/modspec/conformance_class_spec.rb'
@@ -29,21 +69,14 @@ Layout/LineLength:
29
69
  - 'spec/modspec/suite_spec.rb'
30
70
 
31
71
  # Offense count: 4
32
- # Configuration parameters: AllowComments, AllowEmptyLambdas.
33
- Lint/EmptyBlock:
34
- Exclude:
35
- - 'spec/modspec/conformance_class_spec.rb'
36
- - 'spec/modspec/suite_spec.rb'
37
-
38
- # Offense count: 1
39
72
  # This cop supports safe autocorrection (--autocorrect).
40
- # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
41
- # NotImplementedExceptions: NotImplementedError
42
- Lint/UnusedMethodArgument:
73
+ # Configuration parameters: AllowInHeredoc.
74
+ Layout/TrailingWhitespace:
43
75
  Exclude:
44
- - 'lib/modspec/normative_statement.rb'
76
+ - 'lib/modspec/suite.rb'
77
+ - 'spec/modspec/suite_spec.rb'
45
78
 
46
- # Offense count: 7
79
+ # Offense count: 5
47
80
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
48
81
  Metrics/AbcSize:
49
82
  Exclude:
@@ -58,25 +91,20 @@ Metrics/CyclomaticComplexity:
58
91
  # Offense count: 9
59
92
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
60
93
  Metrics/MethodLength:
61
- Max: 21
94
+ Max: 19
62
95
 
63
- # Offense count: 3
96
+ # Offense count: 2
64
97
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
65
98
  Metrics/PerceivedComplexity:
66
99
  Exclude:
67
100
  - 'lib/modspec/suite.rb'
68
101
 
69
- # Offense count: 1
102
+ # Offense count: 2
70
103
  # Configuration parameters: MinSize.
71
104
  Performance/CollectionLiteralInLoop:
72
105
  Exclude:
73
106
  - 'lib/modspec/suite.rb'
74
107
 
75
- # Offense count: 5
76
- # Configuration parameters: CountAsOne.
77
- RSpec/ExampleLength:
78
- Max: 15
79
-
80
108
  # Offense count: 1
81
109
  # This cop supports safe autocorrection (--autocorrect).
82
110
  RSpec/ExpectActual:
@@ -90,7 +118,7 @@ RSpec/IndexedLet:
90
118
  - 'spec/modspec/normative_statements_class_spec.rb'
91
119
  - 'spec/modspec/suite_spec.rb'
92
120
 
93
- # Offense count: 7
121
+ # Offense count: 8
94
122
  RSpec/MultipleExpectations:
95
123
  Max: 9
96
124
 
@@ -103,3 +131,20 @@ RSpec/MultipleMemoizedHelpers:
103
131
  RSpec/RepeatedExample:
104
132
  Exclude:
105
133
  - 'spec/modspec_spec.rb'
134
+
135
+ # Offense count: 1
136
+ # This cop supports safe autocorrection (--autocorrect).
137
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
138
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
139
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
140
+ # FunctionalMethods: let, let!, subject, watch
141
+ # AllowedMethods: lambda, proc, it
142
+ Style/BlockDelimiters:
143
+ Exclude:
144
+ - 'lib/modspec/suite.rb'
145
+
146
+ # Offense count: 2
147
+ # This cop supports safe autocorrection (--autocorrect).
148
+ Style/MultilineIfModifier:
149
+ Exclude:
150
+ - 'lib/modspec/suite.rb'
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "conformance_test"
5
- require_relative "identifier"
6
4
 
7
5
  module Modspec
8
6
  class ConformanceClass < Lutaml::Model::Serializable
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "identifier"
5
4
 
6
5
  module Modspec
7
6
  class ConformanceTest < Lutaml::Model::Serializable
@@ -4,6 +4,5 @@ require "lutaml/model"
4
4
 
5
5
  module Modspec
6
6
  class Identifier < Lutaml::Model::Type::String
7
- # attribute :identifier, :string
8
7
  end
9
8
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "identifier"
5
4
 
6
5
  module Modspec
7
6
  class NormativeStatementPart < Lutaml::Model::Serializable
@@ -46,43 +45,8 @@ module Modspec
46
45
  map_element "parts", to: :parts
47
46
  end
48
47
 
49
- def validate(suite = nil, register: Lutaml::Model::Config.default_register)
50
- errors = super()
51
- errors.concat(validate_dependencies(suite)) if suite
52
- errors.concat(validate_nested_requirement)
53
- errors
54
- end
55
-
56
- private
57
-
58
- def all_dependencies
59
- (
60
- (dependencies || []) +
61
- (indirect_dependency || []) +
62
- (implements || [])
63
- ).flatten.compact
64
- end
65
-
66
- def validate_dependencies(suite)
67
- errors = []
68
- all_identifiers = suite.all_identifiers.map(&:to_s)
69
- all_dependencies.each do |dep|
70
- errors << "Requirement #{identifier} has an invalid dependency: #{dep}" unless all_identifiers.include?(dep)
71
- end
72
- errors
73
- end
74
-
75
- def validate_nested_requirement
76
- if has_parent_requirement?
77
- ["Nested requirement detected: #{identifier}"]
78
- else
79
- []
80
- end
81
- end
82
-
83
- def has_parent_requirement?
84
- # Implementation depends on how you determine if a requirement is nested
85
- false
48
+ def validate(_suite = nil, register: Lutaml::Model::Config.default_register)
49
+ super(register: register)
86
50
  end
87
51
  end
88
52
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "normative_statement"
5
- require_relative "identifier"
6
4
 
7
5
  module Modspec
8
6
  class NormativeStatementsClass < Lutaml::Model::Serializable
data/lib/modspec/suite.rb CHANGED
@@ -95,18 +95,17 @@ module Modspec
95
95
  end
96
96
 
97
97
  def setup_relationships
98
- all_requirements = if normative_statements_classes
99
- normative_statements_classes.flat_map(&:normative_statements)
100
- else
101
- []
102
- end
98
+ return unless normative_statements_classes && conformance_classes
103
99
 
104
- return unless conformance_classes
100
+ req_index = normative_statements_classes
101
+ .flat_map(&:normative_statements)
102
+ .to_h { |r| [r.identifier.to_s, r] }
105
103
 
106
104
  conformance_classes.each do |cc|
107
105
  cc.tests.each do |ct|
108
- ct.corresponding_requirements = all_requirements.select do |r|
109
- Array(ct.targets).map(&:to_s).include?(r.identifier.to_s)
106
+ targets = Array(ct.targets).map(&:to_s)
107
+ ct.corresponding_requirements = targets.filter_map do |t|
108
+ req_index[t]
110
109
  end
111
110
  ct.parent_class = cc
112
111
  end
@@ -171,15 +170,14 @@ module Modspec
171
170
 
172
171
  all_statements.each do |statement|
173
172
  id = statement.identifier.to_s
174
- graph[id] = Set.new
173
+ deps = Set.new
175
174
 
176
- # Define all dependency-like properties to check
177
- dependency_properties = %i[dependencies indirect_dependency implements
178
- targets]
179
-
180
- dependency_properties.each do |property|
181
- graph[id].merge(statement.send(property).map(&:to_s)) if statement.respond_to?(property) && !statement.send(property).nil?
175
+ %i[dependencies indirect_dependency implements targets].each do |prop|
176
+ refs = statement.send(prop) if statement.respond_to?(prop)
177
+ deps.merge(refs.map(&:to_s)) if refs
182
178
  end
179
+
180
+ graph[id] = deps
183
181
  end
184
182
 
185
183
  graph
@@ -205,20 +203,14 @@ module Modspec
205
203
  recursion_stack.add(node)
206
204
  path.push(node)
207
205
 
208
- # Check if the node exists in the graph and has dependencies
209
- if graph[node]
210
- graph[node].each do |neighbor|
211
- if !visited.include?(neighbor)
212
- cycle = detect_cycle_util(neighbor, graph, visited,
213
- recursion_stack, path)
214
- return cycle if cycle
215
- elsif recursion_stack.include?(neighbor)
216
- return path[path.index(neighbor)..] + [neighbor]
217
- end
206
+ graph[node]&.each do |neighbor|
207
+ if !visited.include?(neighbor)
208
+ cycle = detect_cycle_util(neighbor, graph, visited,
209
+ recursion_stack, path)
210
+ return cycle if cycle
211
+ elsif recursion_stack.include?(neighbor)
212
+ return path[path.index(neighbor)..] + [neighbor]
218
213
  end
219
- else
220
- # If the node doesn't exist in the graph, log a warning
221
- puts "Warning: Node #{node} referenced but not found in the graph"
222
214
  end
223
215
 
224
216
  path.pop
@@ -240,20 +232,24 @@ module Modspec
240
232
  end
241
233
 
242
234
  def validate_dependencies
243
- all_identifiers = collect_all_identifiers
235
+ all_ids = collect_all_identifiers
244
236
 
245
237
  errors = []
246
238
  normative_statements_classes&.each do |nsc|
247
- errors.concat(validate_class_dependencies(nsc, all_identifiers))
239
+ errors.concat(validate_refs(nsc, all_ids, :dependencies))
240
+ errors.concat(validate_refs(nsc, all_ids, :implements))
248
241
  nsc.normative_statements.each do |ns|
249
- errors.concat(validate_statement_dependencies(ns, all_identifiers))
242
+ errors.concat(validate_refs(ns, all_ids, :dependencies))
243
+ errors.concat(validate_refs(ns, all_ids, :indirect_dependency))
244
+ errors.concat(validate_refs(ns, all_ids, :implements))
250
245
  end
251
246
  end
252
247
 
253
248
  conformance_classes&.each do |cc|
254
- errors.concat(validate_class_dependencies(cc, all_identifiers))
249
+ errors.concat(validate_refs(cc, all_ids, :dependencies))
255
250
  cc.tests.each do |ct|
256
- errors.concat(validate_test_targets(ct, all_identifiers))
251
+ errors.concat(validate_refs(ct, all_ids, :dependencies))
252
+ errors.concat(validate_refs(ct, all_ids, :targets))
257
253
  end
258
254
  end
259
255
 
@@ -280,28 +276,16 @@ module Modspec
280
276
  identifiers
281
277
  end
282
278
 
283
- def validate_class_dependencies(klass, all_identifiers)
284
- errors = []
285
- klass.dependencies&.each do |dep|
286
- errors << "Invalid dependency #{dep} in #{klass.identifier}" unless all_identifiers.key?(dep.to_s)
287
- end
288
- errors
289
- end
279
+ def validate_refs(obj, all_ids, property)
280
+ refs = obj.send(property)
281
+ return [] unless refs
290
282
 
291
- def validate_statement_dependencies(statement, all_identifiers)
292
- errors = []
293
- statement.dependencies&.each do |dep|
294
- errors << "Invalid dependency #{dep} in #{statement.identifier}" unless all_identifiers.key?(dep.to_s)
295
- end
296
- errors
297
- end
298
-
299
- def validate_test_targets(test, all_identifiers)
300
- errors = []
301
- test.targets&.each do |target|
302
- errors << "Invalid target #{target} in #{test.identifier}" unless all_identifiers.key?(target.to_s)
283
+ refs.filter_map do |ref|
284
+ unless all_ids.key?(ref.to_s)
285
+ "Invalid #{property.to_s.tr('_',
286
+ ' ')} #{ref} in #{obj.identifier}"
287
+ end
303
288
  end
304
- errors
305
289
  end
306
290
  end
307
291
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modspec
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/modspec.rb CHANGED
@@ -5,14 +5,11 @@ require "lutaml/model"
5
5
  require "set"
6
6
 
7
7
  module Modspec
8
- class Error < StandardError; end
9
-
10
- # Your code goes here...
8
+ autoload :Identifier, "modspec/identifier"
9
+ autoload :NormativeStatement, "modspec/normative_statement"
10
+ autoload :NormativeStatementPart, "modspec/normative_statement"
11
+ autoload :NormativeStatementsClass, "modspec/normative_statements_class"
12
+ autoload :ConformanceTest, "modspec/conformance_test"
13
+ autoload :ConformanceClass, "modspec/conformance_class"
14
+ autoload :Suite, "modspec/suite"
11
15
  end
12
-
13
- require_relative "modspec/identifier"
14
- require_relative "modspec/normative_statement"
15
- require_relative "modspec/normative_statements_class"
16
- require_relative "modspec/conformance_test"
17
- require_relative "modspec/conformance_class"
18
- require_relative "modspec/suite"
@@ -11,12 +11,12 @@ RSpec.describe Modspec::ConformanceClass do
11
11
  Modspec::NormativeStatement.new(
12
12
  identifier: "/req/basic-ypr/position",
13
13
  name: "Expression of outer frame",
14
- statement: "The `Basic_YPR.position` attribute shall represent the outer frame, specified by an implicit WGS-84 CRS and an implicit EPSG 4461-CS (LTP-ENU) coordinate system and explicit parameters to define the tangent point.",
14
+ statement: "The `Basic_YPR.position` attribute shall represent the outer frame.",
15
15
  ),
16
16
  Modspec::NormativeStatement.new(
17
17
  identifier: "/req/basic-ypr/angles",
18
18
  name: "Expression of inner frame",
19
- statement: "The `Basic_YPR.angles` attribute shall represent the inner frame, which is a rotation-only transformation with Yaw, Pitch, and Roll (YPR) angles.",
19
+ statement: "The `Basic_YPR.angles` attribute shall represent the inner frame.",
20
20
  ),
21
21
  ],
22
22
  )
@@ -35,7 +35,7 @@ RSpec.describe Modspec::ConformanceClass do
35
35
  identifier: "/conf/basic-ypr/position",
36
36
  name: "Verify expression of outer frame",
37
37
  targets: ["/req/basic-ypr/position"],
38
- description: "To confirm that an implementation of a Basic-YPR consists of an Outer Frame specified by an implicit WGS-84 CRS and an implicit EPSG 4461-CS (LTP-ENU) coordinate system and explicit parameters to define the tangent point.",
38
+ description: "To confirm outer frame.",
39
39
  purpose: "Verify that this requirement is satisfied.",
40
40
  test_method: "Inspection",
41
41
  ),
@@ -43,7 +43,7 @@ RSpec.describe Modspec::ConformanceClass do
43
43
  identifier: "/conf/basic-ypr/angles",
44
44
  name: "Verify expression of inner frame",
45
45
  targets: ["/req/basic-ypr/angles"],
46
- description: "To confirm that the Inner Frame is expressed as a rotation-only transformation using Yaw, Pitch, and Roll angles.",
46
+ description: "To confirm inner frame.",
47
47
  purpose: "Verify that this requirement is satisfied.",
48
48
  test_method: "Inspection",
49
49
  ),
@@ -88,7 +88,7 @@ RSpec.describe Modspec::ConformanceClass do
88
88
  identifier: "/conf/global/sdu",
89
89
  name: "Verify SDU conformance",
90
90
  targets: ["/req/global/sdu"],
91
- description: "To confirm that an implementation of an SDU conforms to the logical model.",
91
+ description: "To confirm SDU conformance.",
92
92
  purpose: "Verify that this requirement is satisfied.",
93
93
  test_method: "Inspection",
94
94
  ),
@@ -105,11 +105,10 @@ RSpec.describe Modspec::ConformanceClass do
105
105
  identifier: "/conf/tangent-point/height",
106
106
  name: "Verify tangent point height",
107
107
  targets: ["/req/tangent-point/height"],
108
- description: "To confirm that an implementation of a Tangent Point specifies the height of the Tangent Point.",
108
+ description: "To confirm tangent point height.",
109
109
  purpose: "Verify that this requirement is satisfied.",
110
110
  test_method: "Inspection",
111
111
  ),
112
-
113
112
  ],
114
113
  )
115
114
  end
@@ -140,17 +139,25 @@ RSpec.describe Modspec::ConformanceClass do
140
139
  describe "#validate" do
141
140
  it "returns no errors for a valid conformance class" do
142
141
  errors = suite.validate
143
- if errors.any?
144
-
145
- errors.each { |error| }
146
- end
147
142
  expect(errors).to be_empty
148
143
  end
149
144
 
150
145
  it "returns errors if there are no conformance tests" do
151
146
  conformance_class.tests = []
152
147
  errors = conformance_class.validate
153
- expect(errors).not_to be_empty
148
+ expect(errors).to include(a_string_matching(/no child conformance tests/))
149
+ end
150
+
151
+ it "returns errors if test identifier does not share prefix" do
152
+ conformance_class.tests = [
153
+ Modspec::ConformanceTest.new(
154
+ identifier: "/conf/other/test",
155
+ name: "Mismatched test",
156
+ targets: ["/req/basic-ypr/position"],
157
+ ),
158
+ ]
159
+ errors = conformance_class.validate
160
+ expect(errors).to include(a_string_matching(/does not share the expected prefix/))
154
161
  end
155
162
  end
156
163
  end
@@ -5,7 +5,7 @@ RSpec.describe Modspec::ConformanceTest do
5
5
  Modspec::NormativeStatement.new(
6
6
  identifier: "/req/basic-ypr/position",
7
7
  name: "Expression of outer frame",
8
- statement: "The `Basic_YPR.position` attribute shall represent the outer frame, specified by an implicit WGS-84 CRS and an implicit EPSG 4461-CS (LTP-ENU) coordinate system and explicit parameters to define the tangent point.",
8
+ statement: "The `Basic_YPR.position` attribute shall represent the outer frame.",
9
9
  )
10
10
  end
11
11
 
@@ -22,7 +22,7 @@ RSpec.describe Modspec::ConformanceTest do
22
22
  identifier: "/conf/basic-ypr/position",
23
23
  name: "Verify expression of outer frame",
24
24
  targets: ["/req/basic-ypr/position"],
25
- description: "To confirm that an implementation of a Basic-YPR consists of an Outer Frame specified by an implicit WGS-84 CRS and an implicit EPSG 4461-CS (LTP-ENU) coordinate system and explicit parameters to define the tangent point.",
25
+ description: "To confirm outer frame.",
26
26
  purpose: "Verify that this requirement is satisfied.",
27
27
  test_method: "Inspection",
28
28
  )
@@ -45,7 +45,7 @@ RSpec.describe Modspec::ConformanceTest do
45
45
  end
46
46
 
47
47
  before do
48
- suite # Ensure the suite is created and relationships are set up
48
+ suite
49
49
  end
50
50
 
51
51
  it "has an identifier" do
@@ -65,6 +65,24 @@ RSpec.describe Modspec::ConformanceTest do
65
65
  errors = conformance_test.validate
66
66
  expect(errors).to be_empty
67
67
  end
68
+
69
+ it "returns errors when corresponding_requirements is nil" do
70
+ conformance_test.corresponding_requirements = nil
71
+ errors = conformance_test.validate
72
+ expect(errors).to include(a_string_matching(/no corresponding requirements/))
73
+ end
74
+
75
+ it "returns errors when corresponding_requirements is empty" do
76
+ conformance_test.corresponding_requirements = []
77
+ errors = conformance_test.validate
78
+ expect(errors).to include(a_string_matching(/no corresponding requirements/))
79
+ end
80
+
81
+ it "returns errors when parent_class is nil" do
82
+ conformance_test.parent_class = nil
83
+ errors = conformance_test.validate
84
+ expect(errors).to include(a_string_matching(/does not belong to its parent class/))
85
+ end
68
86
  end
69
87
 
70
88
  it "has a corresponding requirement" do
@@ -5,7 +5,7 @@ RSpec.describe Modspec::NormativeStatementsClass do
5
5
  Modspec::NormativeStatement.new(
6
6
  identifier: "/req/basic-ypr/position",
7
7
  name: "Expression of outer frame",
8
- statement: "The `Basic_YPR.position` attribute shall represent the outer frame, specified by an implicit WGS-84 CRS and an implicit EPSG 4461-CS (LTP-ENU) coordinate system and explicit parameters to define the tangent point.",
8
+ statement: "The `Basic_YPR.position` attribute shall represent the outer frame.",
9
9
  )
10
10
  end
11
11
 
@@ -13,7 +13,7 @@ RSpec.describe Modspec::NormativeStatementsClass do
13
13
  Modspec::NormativeStatement.new(
14
14
  identifier: "/req/basic-ypr/angles",
15
15
  name: "Expression of inner frame",
16
- statement: "The `Basic_YPR.angles` attribute shall represent the inner frame, which is a rotation-only transformation with Yaw, Pitch, and Roll (YPR) angles.",
16
+ statement: "The `Basic_YPR.angles` attribute shall represent the inner frame.",
17
17
  )
18
18
  end
19
19
 
@@ -21,7 +21,7 @@ RSpec.describe Modspec::NormativeStatementsClass do
21
21
  described_class.new(
22
22
  identifier: "/req/basic-ypr",
23
23
  name: "Basic-YPR logical model SDU",
24
- description: "The Basic-YPR Target has a simple structure with no options. Position is specified as a point in an LTP-ENU frame and rotation is specified by yaw, pitch, and roll angles specified in decimal degrees.",
24
+ description: "The Basic-YPR Target has a simple structure.",
25
25
  dependencies: ["/req/global", "/req/tangent-point"],
26
26
  normative_statements: [normative_statement1, normative_statement2],
27
27
  )
@@ -55,7 +55,19 @@ RSpec.describe Modspec::NormativeStatementsClass do
55
55
  it "returns errors if there are no normative statements" do
56
56
  normative_statements_class.normative_statements = []
57
57
  errors = normative_statements_class.validate
58
- expect(errors).not_to be_empty
58
+ expect(errors).to include(a_string_matching(/no child requirements/))
59
+ end
60
+
61
+ it "returns errors if statement identifier does not share prefix" do
62
+ normative_statements_class.normative_statements = [
63
+ Modspec::NormativeStatement.new(
64
+ identifier: "/req/other/mismatch",
65
+ name: "Mismatched",
66
+ statement: "stmt",
67
+ ),
68
+ ]
69
+ errors = normative_statements_class.validate
70
+ expect(errors).to include(a_string_matching(/does not share the expected prefix/))
59
71
  end
60
72
  end
61
73
  end
@@ -38,12 +38,174 @@ RSpec.describe Modspec::Suite do
38
38
  .combine(frame_spec_suite)
39
39
 
40
40
  errors = combined_suite.validate
41
- if errors.any?
42
-
43
- errors.each { |error| }
44
- end
45
41
  expect(errors).to be_empty
46
42
  end
43
+
44
+ it "detects duplicate identifiers" do
45
+ suite = described_class.new(
46
+ identifier: "/suite",
47
+ name: "Test",
48
+ normative_statements_classes: [
49
+ Modspec::NormativeStatementsClass.new(
50
+ identifier: "/req/test",
51
+ normative_statements: [
52
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
53
+ name: "A", statement: "a"),
54
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
55
+ name: "A2", statement: "a2"),
56
+ ],
57
+ ),
58
+ ],
59
+ conformance_classes: [],
60
+ )
61
+
62
+ errors = suite.validate
63
+ expect(errors).to include(a_string_matching(/Duplicate identifier/))
64
+ end
65
+
66
+ it "detects dependency cycles" do
67
+ ns_a = Modspec::NormativeStatement.new(
68
+ identifier: "/req/test/a", name: "A", statement: "a",
69
+ dependencies: ["/req/test/b"]
70
+ )
71
+ ns_b = Modspec::NormativeStatement.new(
72
+ identifier: "/req/test/b", name: "B", statement: "b",
73
+ dependencies: ["/req/test/a"]
74
+ )
75
+
76
+ suite = described_class.new(
77
+ identifier: "/suite",
78
+ name: "Test",
79
+ normative_statements_classes: [
80
+ Modspec::NormativeStatementsClass.new(
81
+ identifier: "/req/test",
82
+ normative_statements: [ns_a, ns_b],
83
+ ),
84
+ ],
85
+ conformance_classes: [],
86
+ )
87
+
88
+ errors = suite.validate
89
+ expect(errors).to include(a_string_matching(/Cycle detected/))
90
+ end
91
+
92
+ it "detects invalid dependencies" do
93
+ suite = described_class.new(
94
+ identifier: "/suite",
95
+ name: "Test",
96
+ normative_statements_classes: [
97
+ Modspec::NormativeStatementsClass.new(
98
+ identifier: "/req/test",
99
+ dependencies: ["/req/nonexistent"],
100
+ normative_statements: [
101
+ Modspec::NormativeStatement.new(
102
+ identifier: "/req/test/a", name: "A", statement: "a",
103
+ dependencies: ["/req/missing"]
104
+ ),
105
+ ],
106
+ ),
107
+ ],
108
+ conformance_classes: [],
109
+ )
110
+
111
+ errors = suite.validate
112
+ expect(errors).to include(a_string_matching(/Invalid dependencies .* in \/req\/test\b/))
113
+ expect(errors).to include(a_string_matching(/Invalid dependencies .* in \/req\/test\/a/))
114
+ end
115
+
116
+ it "detects invalid conformance test targets" do
117
+ suite = described_class.new(
118
+ identifier: "/suite",
119
+ name: "Test",
120
+ normative_statements_classes: [
121
+ Modspec::NormativeStatementsClass.new(
122
+ identifier: "/req/test",
123
+ normative_statements: [
124
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
125
+ name: "A", statement: "a"),
126
+ ],
127
+ ),
128
+ ],
129
+ conformance_classes: [
130
+ Modspec::ConformanceClass.new(
131
+ identifier: "/conf/test",
132
+ tests: [
133
+ Modspec::ConformanceTest.new(
134
+ identifier: "/conf/test/a",
135
+ name: "CT-A",
136
+ targets: ["/req/nonexistent"],
137
+ ),
138
+ ],
139
+ ),
140
+ ],
141
+ )
142
+
143
+ errors = suite.validate
144
+ expect(errors).to include(a_string_matching(/Invalid targets .* in \/conf\/test\/a/))
145
+ end
146
+
147
+ it "detects invalid indirect_dependency references" do
148
+ suite = described_class.new(
149
+ identifier: "/suite",
150
+ name: "Test",
151
+ normative_statements_classes: [
152
+ Modspec::NormativeStatementsClass.new(
153
+ identifier: "/req/test",
154
+ normative_statements: [
155
+ Modspec::NormativeStatement.new(
156
+ identifier: "/req/test/a", name: "A", statement: "a",
157
+ indirect_dependency: ["/req/ghost"]
158
+ ),
159
+ ],
160
+ ),
161
+ ],
162
+ conformance_classes: [],
163
+ )
164
+
165
+ errors = suite.validate
166
+ expect(errors).to include(a_string_matching(/indirect dependency.*in \/req\/test\/a/))
167
+ end
168
+
169
+ it "detects invalid implements references" do
170
+ suite = described_class.new(
171
+ identifier: "/suite",
172
+ name: "Test",
173
+ normative_statements_classes: [
174
+ Modspec::NormativeStatementsClass.new(
175
+ identifier: "/req/test",
176
+ implements: ["/req/phantom"],
177
+ normative_statements: [],
178
+ ),
179
+ ],
180
+ conformance_classes: [],
181
+ )
182
+
183
+ errors = suite.validate
184
+ expect(errors).to include(a_string_matching(/implements.*in \/req\/test/))
185
+ end
186
+
187
+ it "detects invalid conformance test dependencies" do
188
+ suite = described_class.new(
189
+ identifier: "/suite",
190
+ name: "Test",
191
+ normative_statements_classes: [],
192
+ conformance_classes: [
193
+ Modspec::ConformanceClass.new(
194
+ identifier: "/conf/test",
195
+ tests: [
196
+ Modspec::ConformanceTest.new(
197
+ identifier: "/conf/test/a",
198
+ name: "CT-A",
199
+ dependencies: ["/conf/nonexistent"],
200
+ ),
201
+ ],
202
+ ),
203
+ ],
204
+ )
205
+
206
+ errors = suite.validate
207
+ expect(errors).to include(a_string_matching(/Invalid dependencies .* in \/conf\/test\/a/))
208
+ end
47
209
  end
48
210
 
49
211
  describe "#combine" do
@@ -63,6 +225,16 @@ RSpec.describe Modspec::Suite do
63
225
  )
64
226
  end
65
227
 
228
+ it "raises ArgumentError for non-Suite argument" do
229
+ expect { suite1.combine("not a suite") }.to raise_error(ArgumentError)
230
+ end
231
+
232
+ it "deduplicates by identifier" do
233
+ combined = suite1.combine(suite1)
234
+ nsc_count = combined.normative_statements_classes.count
235
+ expect(nsc_count).to eq(suite1.normative_statements_classes.count)
236
+ end
237
+
66
238
  it "resolves conflicts when combining suites" do
67
239
  combined_suite = suite1
68
240
  .combine(suite2)
@@ -72,10 +244,6 @@ RSpec.describe Modspec::Suite do
72
244
  .combine(frame_spec_suite)
73
245
 
74
246
  errors = combined_suite.validate
75
- if errors.any?
76
-
77
- errors.each { |error| }
78
- end
79
247
  expect(errors).to be_empty
80
248
  end
81
249
  end
@@ -89,24 +257,13 @@ RSpec.describe Modspec::Suite do
89
257
  expect(combined_suite).to be_a(described_class)
90
258
  expect(combined_suite.name).to eq("Combined Suite")
91
259
 
92
- # Ensure the combined suite has content
93
260
  expect(combined_suite.normative_statements_classes).not_to be_empty
94
261
  expect(combined_suite.conformance_classes).not_to be_empty
95
262
 
96
- # Validate the combined suite
97
263
  errors = combined_suite.validate
98
-
99
- if errors.any?
100
-
101
- errors.each { |error| }
102
- end
103
-
104
- # Check for specific error types
105
- expect(errors).not_to include(a_string_matching(/Conformance test .* has no corresponding requirement/))
264
+ expect(errors).not_to include(a_string_matching(/has no corresponding requirement/))
106
265
  expect(errors).not_to include(a_string_matching(/Cycle detected/))
107
- expect(errors).not_to include(a_string_matching(/Requirement .* has an invalid dependency/))
108
-
109
- # If there are still errors, they should be of a different nature
266
+ expect(errors).not_to include(a_string_matching(/has an invalid dependency/))
110
267
  expect(errors).to be_empty
111
268
  end
112
269
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-05 00:00:00.000000000 Z
11
+ date: 2026-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model