capsium 0.1.2 → 0.3.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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +46 -7
  4. data/CHANGELOG.md +183 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +21 -0
  7. data/README.adoc +496 -127
  8. data/Rakefile +8 -1
  9. data/bin/console +1 -1
  10. data/capsium.gemspec +12 -19
  11. data/exe/capsium +1 -1
  12. data/lib/capsium/cli/convert.rb +18 -0
  13. data/lib/capsium/cli/formatting.rb +27 -0
  14. data/lib/capsium/cli/package.rb +148 -0
  15. data/lib/capsium/cli/reactor.rb +33 -0
  16. data/lib/capsium/cli.rb +4 -96
  17. data/lib/capsium/converters/jekyll.rb +5 -7
  18. data/lib/capsium/converters.rb +7 -0
  19. data/lib/capsium/package/authentication.rb +38 -0
  20. data/lib/capsium/package/authentication_config.rb +78 -0
  21. data/lib/capsium/package/cipher.rb +197 -0
  22. data/lib/capsium/package/composition.rb +77 -0
  23. data/lib/capsium/package/dataset.rb +67 -34
  24. data/lib/capsium/package/dependency_resolver.rb +61 -0
  25. data/lib/capsium/package/encryption_config.rb +38 -0
  26. data/lib/capsium/package/manifest.rb +33 -22
  27. data/lib/capsium/package/manifest_config.rb +52 -7
  28. data/lib/capsium/package/merged_view.rb +168 -0
  29. data/lib/capsium/package/metadata.rb +4 -8
  30. data/lib/capsium/package/metadata_config.rb +97 -9
  31. data/lib/capsium/package/preparation.rb +81 -0
  32. data/lib/capsium/package/routes.rb +39 -41
  33. data/lib/capsium/package/routes_config.rb +174 -44
  34. data/lib/capsium/package/security.rb +140 -0
  35. data/lib/capsium/package/security_config.rb +60 -0
  36. data/lib/capsium/package/signer.rb +201 -0
  37. data/lib/capsium/package/storage.rb +20 -45
  38. data/lib/capsium/package/storage_config.rb +128 -3
  39. data/lib/capsium/package/store.rb +109 -0
  40. data/lib/capsium/package/testing/config_test.rb +70 -0
  41. data/lib/capsium/package/testing/context.rb +11 -0
  42. data/lib/capsium/package/testing/data_validation_test.rb +63 -0
  43. data/lib/capsium/package/testing/file_test.rb +45 -0
  44. data/lib/capsium/package/testing/report.rb +33 -0
  45. data/lib/capsium/package/testing/route_test.rb +64 -0
  46. data/lib/capsium/package/testing/test_case.rb +60 -0
  47. data/lib/capsium/package/testing/test_suite.rb +86 -0
  48. data/lib/capsium/package/testing.rb +20 -0
  49. data/lib/capsium/package/validator.rb +160 -0
  50. data/lib/capsium/package/verification.rb +32 -0
  51. data/lib/capsium/package/version.rb +204 -0
  52. data/lib/capsium/package.rb +92 -106
  53. data/lib/capsium/packager.rb +75 -6
  54. data/lib/capsium/reactor/authenticator.rb +180 -0
  55. data/lib/capsium/reactor/deploy.rb +71 -0
  56. data/lib/capsium/reactor/htpasswd.rb +154 -0
  57. data/lib/capsium/reactor/introspection.rb +91 -0
  58. data/lib/capsium/reactor/oauth2.rb +106 -0
  59. data/lib/capsium/reactor/serving.rb +75 -0
  60. data/lib/capsium/reactor/session.rb +82 -0
  61. data/lib/capsium/reactor.rb +93 -43
  62. data/lib/capsium/thor_ext.rb +1 -1
  63. data/lib/capsium/version.rb +1 -1
  64. data/lib/capsium.rb +7 -9
  65. data/sig/capsium/package/authentication.rbs +23 -0
  66. data/sig/capsium/package/authentication_config.rbs +71 -0
  67. data/sig/capsium/package/cipher.rbs +51 -0
  68. data/sig/capsium/package/composition.rbs +20 -0
  69. data/sig/capsium/package/dataset.rbs +28 -0
  70. data/sig/capsium/package/dependency_resolver.rbs +49 -0
  71. data/sig/capsium/package/encryption_config.rbs +35 -0
  72. data/sig/capsium/package/manifest.rbs +34 -0
  73. data/sig/capsium/package/manifest_config.rbs +38 -0
  74. data/sig/capsium/package/merged_view.rbs +46 -0
  75. data/sig/capsium/package/metadata.rbs +26 -0
  76. data/sig/capsium/package/metadata_config.rbs +49 -0
  77. data/sig/capsium/package/preparation.rbs +23 -0
  78. data/sig/capsium/package/routes.rbs +37 -0
  79. data/sig/capsium/package/routes_config.rbs +107 -0
  80. data/sig/capsium/package/security.rbs +72 -0
  81. data/sig/capsium/package/security_config.rbs +57 -0
  82. data/sig/capsium/package/signer.rbs +59 -0
  83. data/sig/capsium/package/storage.rbs +28 -0
  84. data/sig/capsium/package/storage_config.rbs +85 -0
  85. data/sig/capsium/package/store.rbs +37 -0
  86. data/sig/capsium/package/testing/config_test.rbs +21 -0
  87. data/sig/capsium/package/testing/context.rbs +14 -0
  88. data/sig/capsium/package/testing/data_validation_test.rbs +22 -0
  89. data/sig/capsium/package/testing/file_test.rbs +17 -0
  90. data/sig/capsium/package/testing/report.rbs +20 -0
  91. data/sig/capsium/package/testing/route_test.rbs +22 -0
  92. data/sig/capsium/package/testing/test_case.rbs +41 -0
  93. data/sig/capsium/package/testing/test_suite.rbs +20 -0
  94. data/sig/capsium/package/testing.rbs +7 -0
  95. data/sig/capsium/package/validator.rbs +29 -0
  96. data/sig/capsium/package/verification.rbs +24 -0
  97. data/sig/capsium/package/version.rbs +39 -0
  98. data/sig/capsium/package.rbs +61 -0
  99. data/sig/capsium/packager.rbs +32 -0
  100. data/sig/capsium/reactor/authenticator.rbs +40 -0
  101. data/sig/capsium/reactor/deploy.rbs +34 -0
  102. data/sig/capsium/reactor/htpasswd.rbs +36 -0
  103. data/sig/capsium/reactor/introspection.rbs +31 -0
  104. data/sig/capsium/reactor/oauth2.rbs +31 -0
  105. data/sig/capsium/reactor/serving.rbs +22 -0
  106. data/sig/capsium/reactor/session.rbs +36 -0
  107. data/sig/capsium/reactor.rbs +35 -0
  108. data/sig/capsium.rbs +3 -1
  109. metadata +108 -170
  110. data/lib/capsium/package/dataset_config.rb +0 -28
  111. data/lib/capsium/protector.rb +0 -103
@@ -0,0 +1,107 @@
1
+ module Capsium
2
+ class Package
3
+ # The "responseRewrite" object of an inherited route (05x-routing
4
+ # section "Route Inheritance"): replaces the served body and/or
5
+ # overrides response headers.
6
+ class ResponseRewrite
7
+ attr_accessor body: String?
8
+ attr_accessor headers: Hash[String, String]?
9
+
10
+ def initialize: (?body: String? body, ?headers: Hash[String, String]? headers) -> void
11
+
12
+ def to_json: (*untyped args) -> String
13
+ def to_hash: () -> Hash[String, untyped]
14
+ end
15
+
16
+ # A single route entry (ARCHITECTURE.md section 4). Kinds are
17
+ # discriminated by key, MECE: static file, dataset route, or dynamic
18
+ # handler (accepted-and-ignored; reactors respond 501). Route
19
+ # inheritance attributes per 05x-routing.
20
+ class Route
21
+ DATASET_PATH_PREFIX: String
22
+
23
+ attr_accessor path: String?
24
+ attr_accessor resource: String?
25
+ attr_accessor headers: Hash[String, String]?
26
+ attr_accessor headers_file: String?
27
+ attr_accessor visibility: String
28
+ attr_accessor dataset: String?
29
+ attr_accessor access_control: Hash[String, untyped]?
30
+ attr_accessor http_method: String?
31
+ attr_accessor handler: String?
32
+ attr_accessor remap: String?
33
+ attr_accessor response_rewrite: ResponseRewrite?
34
+ attr_accessor response_headers: Hash[String, String]?
35
+ attr_accessor request_headers: Hash[String, String]?
36
+
37
+ def initialize: (?path: String? path, ?resource: String? resource,
38
+ ?headers: Hash[String, String]? headers,
39
+ ?headers_file: String? headers_file,
40
+ ?visibility: String? visibility, ?dataset: String? dataset,
41
+ ?access_control: Hash[String, untyped]? access_control,
42
+ ?http_method: String? http_method,
43
+ ?handler: String? handler, ?remap: String? remap,
44
+ ?response_rewrite: ResponseRewrite? response_rewrite,
45
+ ?response_headers: Hash[String, String]? response_headers,
46
+ ?request_headers: Hash[String, String]? request_headers) -> void
47
+
48
+ def self.from_json: (String json) -> instance
49
+
50
+ def kind: () -> (:resource | :dataset | :handler)
51
+
52
+ def dataset_route?: () -> bool
53
+
54
+ def handler_route?: () -> bool
55
+
56
+ # The URL path this route answers at: the remapped path when the
57
+ # route remaps an inherited route, its own path otherwise.
58
+ def serving_path: () -> String?
59
+
60
+ # Whether the resource addresses content of a dependency package
61
+ # ("<dependency-guid>/<path>" — a URI rather than a
62
+ # package-relative path).
63
+ def dependency_reference?: () -> bool
64
+
65
+ # Whether the route carries route-inheritance attributes.
66
+ def inherited?: () -> bool
67
+
68
+ def fs_path: (String package_path) -> String?
69
+
70
+ def mime: (Manifest manifest) -> String?
71
+
72
+ def validate_target: (String package_path, Storage storage, ?merged_view: MergedView? merged_view) -> void
73
+
74
+ def to_json: (*untyped args) -> String
75
+ def to_hash: () -> Hash[String, untyped]
76
+ end
77
+
78
+ # Canonical routes.json model: optional top-level "index" plus a
79
+ # "routes" array. The legacy object/target form is normalized on
80
+ # read; writers emit only the canonical form.
81
+ class RoutesConfig
82
+ attr_accessor index: String?
83
+ attr_accessor routes: Array[Route]
84
+
85
+ def initialize: (?index: String? index, ?routes: Array[Route] routes) -> void
86
+
87
+ def self.from_json: (String json) -> instance
88
+
89
+ def routes_from_json: (RoutesConfig model, untyped value) -> void
90
+
91
+ def routes_to_json: (RoutesConfig model, Hash[String, untyped] doc) -> void
92
+
93
+ def resolve: (String path) -> Route?
94
+
95
+ def add: (String path, String target) -> Route
96
+
97
+ def update: (String path, String updated_path, String updated_target) -> Route
98
+
99
+ def remove: (String path) -> Array[Route]
100
+
101
+ def sort!: () -> RoutesConfig
102
+
103
+ def to_json: (*untyped args) -> String
104
+ def to_hash: () -> Hash[String, untyped]
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,72 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads, generates and verifies security.json (ARCHITECTURE.md
4
+ # section 6). Checksums cover every file in the package except
5
+ # security.json itself and signature.sig.
6
+ class Security
7
+ class IntegrityError < Capsium::Error
8
+ end
9
+
10
+ # A file whose SHA-256 does not match the recorded checksum.
11
+ class ChecksumMismatch
12
+ attr_reader path: String
13
+ attr_reader expected: String
14
+ attr_reader actual: String
15
+
16
+ def initialize: (path: String path, expected: String expected, actual: String actual) -> void
17
+
18
+ def message: () -> String
19
+ end
20
+
21
+ # A checksum listed for a file that is missing on disk.
22
+ class MissingFile
23
+ attr_reader path: String
24
+ attr_reader expected: String
25
+
26
+ def initialize: (path: String path, expected: String expected) -> void
27
+
28
+ def message: () -> String
29
+ end
30
+
31
+ # A file on disk not covered by any checksum.
32
+ class UncheckedFile
33
+ attr_reader path: String
34
+
35
+ def initialize: (path: String path) -> void
36
+
37
+ def message: () -> String
38
+ end
39
+
40
+ attr_reader path: String
41
+ attr_reader config: SecurityConfig?
42
+
43
+ def initialize: (String path, ?SecurityConfig? config) -> void
44
+
45
+ def self.generate: (String package_path, ?digital_signatures: DigitalSignatures? digital_signatures) -> Security
46
+
47
+ def self.checksums_for: (String package_path) -> Hash[String, String]
48
+
49
+ def self.package_files: (String package_path) -> Array[String]
50
+
51
+ def self.excluded?: (String relative_path) -> bool
52
+
53
+ def present?: () -> bool
54
+
55
+ # The declared digitalSignatures block, or nil when absent.
56
+ def digital_signatures: () -> DigitalSignatures?
57
+
58
+ def signed?: () -> bool
59
+
60
+ def checksums: () -> Hash[String, String]
61
+
62
+ def verify: (String package_path) -> Array[ChecksumMismatch | MissingFile | UncheckedFile]
63
+
64
+ def verify!: (String package_path) -> void
65
+
66
+ def save_to_file: (?String output_path) -> void
67
+
68
+ def to_json: (*untyped args) -> String
69
+ def to_hash: () -> Hash[String, untyped]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,57 @@
1
+ module Capsium
2
+ class Package
3
+ # The "integrityChecks" object of security.json: SHA-256 checksums
4
+ # keyed by package-relative path.
5
+ class IntegrityChecks
6
+ ALGORITHMS: Array[String]
7
+
8
+ attr_accessor checksum_algorithm: String
9
+ attr_accessor checksums: Hash[String, String]
10
+
11
+ def initialize: (?checksum_algorithm: String? checksum_algorithm,
12
+ ?checksums: Hash[String, String] checksums) -> void
13
+
14
+ def to_json: (*untyped args) -> String
15
+ def to_hash: () -> Hash[String, untyped]
16
+ end
17
+
18
+ # The "digitalSignatures" object of security.json (parsed only;
19
+ # signing is a later phase).
20
+ class DigitalSignatures
21
+ attr_accessor public_key: String?
22
+ attr_accessor signature_file: String?
23
+
24
+ def initialize: (?public_key: String? public_key,
25
+ ?signature_file: String? signature_file) -> void
26
+
27
+ def to_json: (*untyped args) -> String
28
+ def to_hash: () -> Hash[String, untyped]
29
+ end
30
+
31
+ # The "security" object of security.json.
32
+ class SecurityData
33
+ attr_accessor integrity_checks: IntegrityChecks?
34
+ attr_accessor digital_signatures: DigitalSignatures?
35
+
36
+ def initialize: (?integrity_checks: IntegrityChecks? integrity_checks,
37
+ ?digital_signatures: DigitalSignatures? digital_signatures) -> void
38
+
39
+ def to_json: (*untyped args) -> String
40
+ def to_hash: () -> Hash[String, untyped]
41
+ end
42
+
43
+ # Canonical security.json model (generated at pack time).
44
+ class SecurityConfig
45
+ attr_accessor security: SecurityData?
46
+
47
+ def initialize: (?security: SecurityData? security) -> void
48
+
49
+ def self.from_json: (String json) -> instance
50
+
51
+ def checksums: () -> Hash[String, String]
52
+
53
+ def to_json: (*untyped args) -> String
54
+ def to_hash: () -> Hash[String, untyped]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,59 @@
1
+ module Capsium
2
+ class Package
3
+ # Signs and verifies Capsium packages with RSA-SHA256 digital
4
+ # signatures (05x-packaging "Digital Signature Using X.509",
5
+ # 05x-security "Digital Signatures"). The signed payload is the
6
+ # concatenation, in sorted package-relative path order, of the raw
7
+ # bytes of every file covered by the security.json integrity
8
+ # checksums (everything except security.json and signature.sig).
9
+ class Signer
10
+ ALGORITHM: String
11
+ MIN_KEY_BITS: Integer
12
+
13
+ # Package-relative name of the embedded public key PEM recorded in
14
+ # security.json digitalSignatures.publicKey.
15
+ PUBLIC_KEY_FILE: String
16
+
17
+ # Structural problems: no signature declared, missing signature or
18
+ # key files, unloadable keys, checksum-covered files missing.
19
+ class SignatureError < Capsium::Error
20
+ end
21
+
22
+ # A signature is declared but does not match the package contents.
23
+ class SignatureMismatchError < SignatureError
24
+ end
25
+
26
+ # Signature operations requested on an unsigned package.
27
+ class UnsignedPackageError < SignatureError
28
+ end
29
+
30
+ attr_reader package_path: String
31
+
32
+ def initialize: (String package_path) -> void
33
+
34
+ # Signs a package directory in place, or a .cap file (unpacked,
35
+ # signed, recompressed). Returns the signed path.
36
+ def self.sign_package: (String path, String private_key_path, ?String? certificate_path) -> String
37
+
38
+ # Verifies the declared signature of a package directory or .cap
39
+ # file (false on mismatch; raises typed SignatureError subclasses
40
+ # on structural problems, e.g. an unsigned package).
41
+ def self.verify_package: (String path, ?String? public_key_path) -> bool
42
+
43
+ # Signs the package directory in place: embeds the public key PEM,
44
+ # regenerates security.json (checksums plus digitalSignatures) and
45
+ # writes the raw RSA-SHA256 signature to signature.sig. Returns the
46
+ # path of the written signature file.
47
+ def sign: (String private_key_path, ?String? certificate_path) -> String
48
+
49
+ # True when the declared signature verifies against the payload;
50
+ # false on mismatch. Raises typed SignatureError subclasses on
51
+ # structural problems.
52
+ def verify: (?String? public_key_path) -> bool
53
+
54
+ def verify!: (?String? public_key_path) -> bool
55
+
56
+ def signed?: () -> bool
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads and writes storage.json and instantiates its datasets
4
+ # (ARCHITECTURE.md section 5).
5
+ class Storage
6
+ attr_reader path: String
7
+ attr_reader config: StorageConfig
8
+ attr_reader datasets: Array[Dataset]
9
+
10
+ def initialize: (String path) -> void
11
+
12
+ def data_sets: () -> Hash[String, DatasetConfig]
13
+
14
+ def load_datasets: () -> Array[Dataset]
15
+
16
+ def dataset: (String name) -> Dataset?
17
+
18
+ def dataset_names: () -> Array[String]
19
+
20
+ def empty?: () -> bool
21
+
22
+ def save_to_file: (?String output_path) -> void
23
+
24
+ def to_json: (*untyped args) -> String
25
+ def to_hash: () -> Hash[String, untyped]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,85 @@
1
+ module Capsium
2
+ class Package
3
+ # A single storage layer entry (ARCHITECTURE.md section 5a): a
4
+ # package-relative directory mirroring the content/ tree. Layers are
5
+ # stacked bottom -> top in declaration order; "visibility: private"
6
+ # layers are hidden from dependent packages.
7
+ class LayerConfig
8
+ attr_accessor path: String?
9
+ attr_accessor writable: bool
10
+ attr_accessor visibility: String
11
+
12
+ def initialize: (?path: String? path, ?writable: bool writable,
13
+ ?visibility: String? visibility) -> void
14
+
15
+ def private?: () -> bool
16
+
17
+ def to_json: (*untyped args) -> String
18
+ def to_hash: () -> Hash[String, untyped]
19
+ end
20
+
21
+ # A single dataset entry (ARCHITECTURE.md section 5): schema-backed
22
+ # file (via "source") or SQLite ("databaseFile" + "table").
23
+ class DatasetConfig
24
+ FORMATS: Hash[String, String]
25
+ SCHEMA_TYPES: Array[String]
26
+
27
+ attr_accessor source: String?
28
+ attr_accessor schema_file: String?
29
+ attr_accessor schema_type: String?
30
+ attr_accessor database_file: String?
31
+ attr_accessor table: String?
32
+
33
+ def initialize: (?source: String? source, ?schema_file: String? schema_file,
34
+ ?schema_type: String? schema_type,
35
+ ?database_file: String? database_file,
36
+ ?table: String? table) -> void
37
+
38
+ def format: () -> String
39
+
40
+ def sqlite?: () -> bool
41
+
42
+ def backing_file: () -> String
43
+
44
+ def to_dataset: (String name, String package_path) -> Dataset
45
+
46
+ def to_json: (*untyped args) -> String
47
+ def to_hash: () -> Hash[String, untyped]
48
+ end
49
+
50
+ # The "storage" object holding the dataSets map and the layers stack.
51
+ class StorageData
52
+ attr_accessor data_sets: Hash[String, DatasetConfig]
53
+ attr_accessor layers: Array[LayerConfig]
54
+
55
+ def initialize: (?data_sets: Hash[String, DatasetConfig] data_sets,
56
+ ?layers: Array[LayerConfig] layers) -> void
57
+
58
+ def data_sets_from_json: (StorageData model, untyped value) -> void
59
+
60
+ def data_sets_to_json: (StorageData model, Hash[String, untyped] doc) -> void
61
+
62
+ def to_json: (*untyped args) -> String
63
+ def to_hash: () -> Hash[String, untyped]
64
+ end
65
+
66
+ # Canonical storage.json model. The legacy array form is normalized
67
+ # on read; writers emit only the canonical form.
68
+ class StorageConfig
69
+ attr_accessor storage: StorageData?
70
+
71
+ def initialize: (?storage: StorageData? storage) -> void
72
+
73
+ def self.from_json: (String json) -> instance
74
+
75
+ def data_sets: () -> Hash[String, DatasetConfig]
76
+
77
+ # The configured storage layers, bottom -> top (empty when the
78
+ # package uses the single implicit content/ layer).
79
+ def layers: () -> Array[LayerConfig]
80
+
81
+ def to_json: (*untyped args) -> String
82
+ def to_hash: () -> Hash[String, untyped]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,37 @@
1
+ module Capsium
2
+ class Package
3
+ # A package store directory (ARCHITECTURE.md section 4a):
4
+ # "<name>-<version>.cap" files plus an optional index.json mapping
5
+ # dependency GUID -> file name. Dependencies resolve to the newest
6
+ # version satisfying their semver range.
7
+ class Store
8
+ INDEX_FILE: String
9
+ CAP_GLOB: String
10
+
11
+ # One store .cap with its identity per its metadata.json.
12
+ class CatalogEntry
13
+ def self.new: (String path, String? guid, Version version) -> void
14
+
15
+ def path: () -> String
16
+ def guid: () -> String?
17
+ def version: () -> Version
18
+ end
19
+
20
+ attr_reader dir: String
21
+
22
+ # The store configured via the CAPSIUM_STORE environment variable,
23
+ # or nil when unset.
24
+ def self.default: () -> Store?
25
+
26
+ def initialize: (String dir) -> void
27
+
28
+ # The newest .cap providing the dependency GUID whose version
29
+ # satisfies the range string. Raises DependencyNotFoundError or
30
+ # UnsatisfiableDependencyError when it cannot.
31
+ def find: (String guid, String range_string) -> String
32
+
33
+ # Every .cap in the store with its metadata identity (memoized).
34
+ def catalog: () -> Array[CatalogEntry]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "config" test (05x-testing): the configuration file must exist
5
+ # in the package and parse as the declared format; known Capsium
6
+ # package configs are additionally validated against their
7
+ # canonical models.
8
+ class ConfigTest < TestCase
9
+ FORMATS: Array[String]
10
+
11
+ attr_reader format: String
12
+ attr_reader config_file: String
13
+
14
+ def initialize: (name: String name, format: String format,
15
+ config_file: String config_file) -> void
16
+
17
+ def run: (Context context) -> TestCase::Result
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # What a test run needs: the extracted package directory and, when
5
+ # route tests run, the base URL of the reactor serving the package.
6
+ class Context
7
+ attr_reader package_path: String
8
+ attr_reader base_url: String?
9
+
10
+ def initialize: (package_path: String package_path, base_url: String? base_url) -> void
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "data_validation" test (05x-testing): the rows of the data
5
+ # file (format: json or yaml) must validate against the JSON
6
+ # schema file.
7
+ class DataValidationTest < TestCase
8
+ FORMATS: Array[String]
9
+
10
+ attr_reader format: String
11
+ attr_reader data_file: String
12
+ attr_reader schema_file: String
13
+
14
+ def initialize: (name: String name, format: String format,
15
+ data_file: String data_file,
16
+ schema_file: String schema_file) -> void
17
+
18
+ def run: (Context context) -> TestCase::Result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "file" test (05x-testing): the file must exist in the package;
5
+ # optionally its content must contain a string.
6
+ class FileTest < TestCase
7
+ attr_reader path: String
8
+ attr_reader contains: String?
9
+
10
+ def initialize: (name: String name, path: String path,
11
+ ?contains: String? contains) -> void
12
+
13
+ def run: (Context context) -> TestCase::Result
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # The outcome of a test suite run: one Result per test case.
5
+ class Report
6
+ attr_reader results: Array[TestCase::Result]
7
+
8
+ def initialize: (?Array[TestCase::Result] results) -> void
9
+
10
+ def <<: (TestCase::Result result) -> Report
11
+
12
+ def ok?: () -> bool
13
+
14
+ def failures: () -> Array[TestCase::Result]
15
+
16
+ def summary: () -> String
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "route" test (05x-testing): requests the URL path from the
5
+ # reactor serving the package and checks the expected status, and
6
+ # optionally the content type and a response body substring.
7
+ class RouteTest < TestCase
8
+ attr_reader url: String
9
+ attr_reader expected_status: Integer
10
+ attr_reader response_contains: String?
11
+ attr_reader expected_content_type: String?
12
+
13
+ def initialize: (name: String name, url: String url,
14
+ expected_status: Integer expected_status,
15
+ ?response_contains: String? response_contains,
16
+ ?expected_content_type: String? expected_content_type) -> void
17
+
18
+ def run: (Context context) -> TestCase::Result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # Base class for one test of the Capsium testing YAML DSL
5
+ # (05x-testing). Subclasses implement a test kind and register
6
+ # themselves under their DSL type name (open/closed registry).
7
+ class TestCase
8
+ # The outcome of running one test.
9
+ class Result
10
+ attr_reader name: String
11
+ attr_reader ok: bool
12
+ attr_reader messages: Array[String]
13
+
14
+ def initialize: (name: String name, ok: bool ok, messages: Array[String] messages) -> void
15
+
16
+ def ok?: () -> bool
17
+ end
18
+
19
+ # Raised for invalid test definitions.
20
+ class DefinitionError < Capsium::Error
21
+ end
22
+
23
+ attr_reader name: String
24
+
25
+ def initialize: (name: String name) -> void
26
+
27
+ # Runs the test against the context and returns a Result.
28
+ def run: (Context context) -> Result
29
+
30
+ def self.register: (String type, singleton(TestCase) klass) -> void
31
+
32
+ def self.types: () -> Hash[String, singleton(TestCase)]
33
+
34
+ # Builds a test case from a YAML definition hash (string keys).
35
+ def self.build: (untyped definition) -> TestCase
36
+
37
+ def self.from_h: (Hash[String, untyped] definition) -> TestCase
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # Loads and runs the package's tests/*.yaml suites (05x-testing).
5
+ # Route tests are served by a reactor on an ephemeral port for the
6
+ # duration of the run.
7
+ class TestSuite
8
+ TESTS_DIR: String
9
+
10
+ attr_reader package: Capsium::Package
11
+
12
+ def initialize: (Capsium::Package package) -> void
13
+
14
+ def test_files: () -> Array[String]
15
+
16
+ def run: () -> Report
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module Capsium
2
+ class Package
3
+ # Runner for the Capsium package testing YAML DSL (05x-testing).
4
+ module Testing
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Capsium
2
+ class Package
3
+ # Validates a package directory or .cap file against the canonical
4
+ # schemas (ARCHITECTURE.md sections 2-6) and reports one result per
5
+ # check. Used by `capsium package validate`.
6
+ class Validator
7
+ EXTERNAL_REFERENCE_PATTERN: Regexp
8
+ TEXT_CONTENT_MIME: Regexp
9
+
10
+ # One named validation check with its problems; ok when messages
11
+ # is empty.
12
+ class CheckResult
13
+ attr_reader name: String
14
+ attr_reader ok: bool
15
+ attr_reader messages: Array[String]
16
+
17
+ def initialize: (name: String name, ok: bool ok, messages: Array[String] messages) -> void
18
+
19
+ def ok?: () -> bool
20
+ end
21
+
22
+ def initialize: (String package_path) -> void
23
+
24
+ def run: () -> Array[CheckResult]
25
+
26
+ def valid?: () -> bool
27
+ end
28
+ end
29
+ end