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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +46 -7
- data/CHANGELOG.md +183 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.adoc +496 -127
- data/Rakefile +8 -1
- data/bin/console +1 -1
- data/capsium.gemspec +12 -19
- data/exe/capsium +1 -1
- data/lib/capsium/cli/convert.rb +18 -0
- data/lib/capsium/cli/formatting.rb +27 -0
- data/lib/capsium/cli/package.rb +148 -0
- data/lib/capsium/cli/reactor.rb +33 -0
- data/lib/capsium/cli.rb +4 -96
- data/lib/capsium/converters/jekyll.rb +5 -7
- data/lib/capsium/converters.rb +7 -0
- data/lib/capsium/package/authentication.rb +38 -0
- data/lib/capsium/package/authentication_config.rb +78 -0
- data/lib/capsium/package/cipher.rb +197 -0
- data/lib/capsium/package/composition.rb +77 -0
- data/lib/capsium/package/dataset.rb +67 -34
- data/lib/capsium/package/dependency_resolver.rb +61 -0
- data/lib/capsium/package/encryption_config.rb +38 -0
- data/lib/capsium/package/manifest.rb +33 -22
- data/lib/capsium/package/manifest_config.rb +52 -7
- data/lib/capsium/package/merged_view.rb +168 -0
- data/lib/capsium/package/metadata.rb +4 -8
- data/lib/capsium/package/metadata_config.rb +97 -9
- data/lib/capsium/package/preparation.rb +81 -0
- data/lib/capsium/package/routes.rb +39 -41
- data/lib/capsium/package/routes_config.rb +174 -44
- data/lib/capsium/package/security.rb +140 -0
- data/lib/capsium/package/security_config.rb +60 -0
- data/lib/capsium/package/signer.rb +201 -0
- data/lib/capsium/package/storage.rb +20 -45
- data/lib/capsium/package/storage_config.rb +128 -3
- data/lib/capsium/package/store.rb +109 -0
- data/lib/capsium/package/testing/config_test.rb +70 -0
- data/lib/capsium/package/testing/context.rb +11 -0
- data/lib/capsium/package/testing/data_validation_test.rb +63 -0
- data/lib/capsium/package/testing/file_test.rb +45 -0
- data/lib/capsium/package/testing/report.rb +33 -0
- data/lib/capsium/package/testing/route_test.rb +64 -0
- data/lib/capsium/package/testing/test_case.rb +60 -0
- data/lib/capsium/package/testing/test_suite.rb +86 -0
- data/lib/capsium/package/testing.rb +20 -0
- data/lib/capsium/package/validator.rb +160 -0
- data/lib/capsium/package/verification.rb +32 -0
- data/lib/capsium/package/version.rb +204 -0
- data/lib/capsium/package.rb +92 -106
- data/lib/capsium/packager.rb +75 -6
- data/lib/capsium/reactor/authenticator.rb +180 -0
- data/lib/capsium/reactor/deploy.rb +71 -0
- data/lib/capsium/reactor/htpasswd.rb +154 -0
- data/lib/capsium/reactor/introspection.rb +91 -0
- data/lib/capsium/reactor/oauth2.rb +106 -0
- data/lib/capsium/reactor/serving.rb +75 -0
- data/lib/capsium/reactor/session.rb +82 -0
- data/lib/capsium/reactor.rb +93 -43
- data/lib/capsium/thor_ext.rb +1 -1
- data/lib/capsium/version.rb +1 -1
- data/lib/capsium.rb +7 -9
- data/sig/capsium/package/authentication.rbs +23 -0
- data/sig/capsium/package/authentication_config.rbs +71 -0
- data/sig/capsium/package/cipher.rbs +51 -0
- data/sig/capsium/package/composition.rbs +20 -0
- data/sig/capsium/package/dataset.rbs +28 -0
- data/sig/capsium/package/dependency_resolver.rbs +49 -0
- data/sig/capsium/package/encryption_config.rbs +35 -0
- data/sig/capsium/package/manifest.rbs +34 -0
- data/sig/capsium/package/manifest_config.rbs +38 -0
- data/sig/capsium/package/merged_view.rbs +46 -0
- data/sig/capsium/package/metadata.rbs +26 -0
- data/sig/capsium/package/metadata_config.rbs +49 -0
- data/sig/capsium/package/preparation.rbs +23 -0
- data/sig/capsium/package/routes.rbs +37 -0
- data/sig/capsium/package/routes_config.rbs +107 -0
- data/sig/capsium/package/security.rbs +72 -0
- data/sig/capsium/package/security_config.rbs +57 -0
- data/sig/capsium/package/signer.rbs +59 -0
- data/sig/capsium/package/storage.rbs +28 -0
- data/sig/capsium/package/storage_config.rbs +85 -0
- data/sig/capsium/package/store.rbs +37 -0
- data/sig/capsium/package/testing/config_test.rbs +21 -0
- data/sig/capsium/package/testing/context.rbs +14 -0
- data/sig/capsium/package/testing/data_validation_test.rbs +22 -0
- data/sig/capsium/package/testing/file_test.rbs +17 -0
- data/sig/capsium/package/testing/report.rbs +20 -0
- data/sig/capsium/package/testing/route_test.rbs +22 -0
- data/sig/capsium/package/testing/test_case.rbs +41 -0
- data/sig/capsium/package/testing/test_suite.rbs +20 -0
- data/sig/capsium/package/testing.rbs +7 -0
- data/sig/capsium/package/validator.rbs +29 -0
- data/sig/capsium/package/verification.rbs +24 -0
- data/sig/capsium/package/version.rbs +39 -0
- data/sig/capsium/package.rbs +61 -0
- data/sig/capsium/packager.rbs +32 -0
- data/sig/capsium/reactor/authenticator.rbs +40 -0
- data/sig/capsium/reactor/deploy.rbs +34 -0
- data/sig/capsium/reactor/htpasswd.rbs +36 -0
- data/sig/capsium/reactor/introspection.rbs +31 -0
- data/sig/capsium/reactor/oauth2.rbs +31 -0
- data/sig/capsium/reactor/serving.rbs +22 -0
- data/sig/capsium/reactor/session.rbs +36 -0
- data/sig/capsium/reactor.rbs +35 -0
- data/sig/capsium.rbs +3 -1
- metadata +108 -170
- data/lib/capsium/package/dataset_config.rb +0 -28
- 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,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
|