govuk_schemas 3.3.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +17 -0
- data/.rubocop.yml +5 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -1
- data/Jenkinsfile +3 -45
- data/govuk_schemas.gemspec +7 -8
- data/lib/govuk_schemas.rb +1 -2
- data/lib/govuk_schemas/random_content_generator.rb +128 -0
- data/lib/govuk_schemas/random_example.rb +9 -4
- data/lib/govuk_schemas/{random_item_generator.rb → random_schema_generator.rb} +65 -48
- data/lib/govuk_schemas/rspec_matchers.rb +18 -27
- data/lib/govuk_schemas/schema.rb +2 -3
- data/lib/govuk_schemas/version.rb +1 -1
- metadata +23 -24
- data/lib/govuk_schemas/random.rb +0 -101
- data/lib/govuk_schemas/utils.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3b1010c2e3fc4d2b11f832cb7fc987d93bc31e911847ec6d18231e47a29a605f
|
4
|
+
data.tar.gz: 1c7c41a441859658c3f2e53ba4399374227120611150d14ca9a0d0f850e24c15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44e53cbd69991f6882e7d9a371257bef45fc100c75f945048e8700fdb9c17c9a69727219206e7ce720b5007c5412d7139472d370f856cc46fc1387453432f09b
|
7
|
+
data.tar.gz: 8ad00bb818a94cb75c5ae2d0e99ab754a204655a966a11c4acbed2e937c3015794a2847c9c5b6eb8567d2f724893c03a98fe2252c14b2499cc8d9a93b7b2e26b
|
@@ -0,0 +1,17 @@
|
|
1
|
+
version: 2
|
2
|
+
updates:
|
3
|
+
- package-ecosystem: bundler
|
4
|
+
directory: /
|
5
|
+
schedule:
|
6
|
+
interval: daily
|
7
|
+
allow:
|
8
|
+
# Internal gems
|
9
|
+
- dependency-name: "govuk*"
|
10
|
+
dependency-type: direct
|
11
|
+
- dependency-name: rubocop-govuk
|
12
|
+
dependency-type: direct
|
13
|
+
# Framework gems
|
14
|
+
- dependency-name: rake
|
15
|
+
dependency-type: direct
|
16
|
+
- dependency-name: rspec
|
17
|
+
dependency-type: direct
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
# 4.3.0
|
2
|
+
|
3
|
+
* Generate unique items for arrays with the "uniqueItems" property. ([#63](https://github.com/alphagov/govuk_schemas/pull/63))
|
4
|
+
|
5
|
+
# 4.2.0
|
6
|
+
|
7
|
+
* Add support for generating random HH:MM time strings that match a regex. ([#62](https://github.com/alphagov/govuk_schemas/pull/62))
|
8
|
+
|
9
|
+
# 4.1.1
|
10
|
+
|
11
|
+
* Fix RandomSchemaGenerator.new always returning equivalent generators ([#60](https://github.com/alphagov/govuk_schemas/pull/60))
|
12
|
+
|
13
|
+
# 4.1.0
|
14
|
+
|
15
|
+
* Add `seed` parameter to `GovukSchemas::RandomExample` to make the random behaviour deterministic. Given the same seed, the same randomised outputs will be returned ([#56](https://github.com/alphagov/govuk_schemas/pull/56)).
|
16
|
+
|
17
|
+
# 4.0.1
|
18
|
+
|
19
|
+
* Bump the required Ruby version to >= 2.6.x.
|
20
|
+
|
21
|
+
# 4.0.0
|
22
|
+
|
23
|
+
* Change RSpec::Matchers, rename `be_valid_against_schema` to
|
24
|
+
`be_valid_against_publisher_schema` and add
|
25
|
+
`be_valid_against_frontend_schema` plus
|
26
|
+
`be_valid_against_notification_schema`.
|
27
|
+
|
1
28
|
# 3.3.0
|
2
29
|
|
3
30
|
* Support generating objects with an `oneOf` property.
|
data/Gemfile
CHANGED
data/Jenkinsfile
CHANGED
@@ -2,50 +2,8 @@
|
|
2
2
|
|
3
3
|
library("govuk")
|
4
4
|
|
5
|
-
REPOSITORY = 'govuk_schemas'
|
6
|
-
|
7
5
|
node {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
checkout scm
|
12
|
-
}
|
13
|
-
|
14
|
-
stage('Clean') {
|
15
|
-
govuk.cleanupGit()
|
16
|
-
govuk.mergeMasterBranch()
|
17
|
-
}
|
18
|
-
|
19
|
-
stage("Set up content schema dependency") {
|
20
|
-
govuk.contentSchemaDependency()
|
21
|
-
}
|
22
|
-
|
23
|
-
stage('Bundle') {
|
24
|
-
echo 'Bundling'
|
25
|
-
sh("bundle install --path ${JENKINS_HOME}/bundles/${JOB_NAME}")
|
26
|
-
}
|
27
|
-
|
28
|
-
stage('Linter') {
|
29
|
-
govuk.rubyLinter()
|
30
|
-
}
|
31
|
-
|
32
|
-
stage('Tests') {
|
33
|
-
govuk.setEnvar('RAILS_ENV', 'test')
|
34
|
-
govuk.runTests('spec')
|
35
|
-
}
|
36
|
-
|
37
|
-
if(env.BRANCH_NAME == "master") {
|
38
|
-
stage('Publish Gem') {
|
39
|
-
govuk.publishGem(REPOSITORY, env.BRANCH_NAME)
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
} catch (e) {
|
44
|
-
currentBuild.result = 'FAILED'
|
45
|
-
step([$class: 'Mailer',
|
46
|
-
notifyEveryUnstableBuild: true,
|
47
|
-
recipients: 'govuk-ci-notifications@digital.cabinet-office.gov.uk',
|
48
|
-
sendToIndividuals: true])
|
49
|
-
throw e
|
50
|
-
}
|
6
|
+
govuk.buildProject(
|
7
|
+
rubyLintDiff: false,
|
8
|
+
)
|
51
9
|
}
|
data/govuk_schemas.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "govuk_schemas/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "govuk_schemas"
|
@@ -17,16 +16,16 @@ Gem::Specification.new do |spec|
|
|
17
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
17
|
spec.bindir = "exe"
|
19
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = %w[lib]
|
21
20
|
|
22
21
|
# This should be kept in sync with the json-schema version of govuk-content-schemas.
|
23
22
|
spec.add_dependency "json-schema", "~> 2.8.0"
|
24
23
|
|
25
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
-
spec.add_development_dependency "rspec", "~> 3.4"
|
27
24
|
spec.add_development_dependency "pry-byebug"
|
28
|
-
spec.add_development_dependency "
|
25
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
27
|
+
spec.add_development_dependency "rubocop-govuk", "~> 3.8"
|
29
28
|
spec.add_development_dependency "yard", "~> 0.8"
|
30
29
|
|
31
|
-
spec.required_ruby_version = ">= 2.
|
30
|
+
spec.required_ruby_version = ">= 2.6"
|
32
31
|
end
|
data/lib/govuk_schemas.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require "govuk_schemas/version"
|
2
2
|
require "govuk_schemas/schema"
|
3
|
-
require "govuk_schemas/utils"
|
4
3
|
require "govuk_schemas/random_example"
|
5
4
|
require "govuk_schemas/document_types"
|
6
5
|
require "govuk_schemas/example"
|
@@ -10,6 +9,6 @@ module GovukSchemas
|
|
10
9
|
CONTENT_SCHEMA_DIR = ENV["GOVUK_CONTENT_SCHEMAS_PATH"] || "../govuk-content-schemas"
|
11
10
|
|
12
11
|
# @private
|
13
|
-
class InvalidContentGenerated <
|
12
|
+
class InvalidContentGenerated < RuntimeError
|
14
13
|
end
|
15
14
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module GovukSchemas
|
2
|
+
# @private
|
3
|
+
class RandomContentGenerator
|
4
|
+
WORDS = %w[Lorem ipsum dolor sit amet consectetur adipiscing elit. Ut suscipit at mauris non bibendum. Ut ac massa est. Aenean tempor imperdiet leo vel interdum. Nam sagittis cursus sem ultricies scelerisque. Quisque porttitor risus vel risus finibus eu sollicitudin nisl aliquet. Sed sed lectus ac dolor molestie interdum. Nam molestie pellentesque purus ac vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse non tempor eros. Mauris eu orci hendrerit volutpat lorem in tristique libero. Duis a nibh nibh.].freeze
|
5
|
+
|
6
|
+
def initialize(random: Random.new)
|
7
|
+
@random = random
|
8
|
+
end
|
9
|
+
|
10
|
+
def string_for_type(type)
|
11
|
+
if type == "date-time"
|
12
|
+
time
|
13
|
+
elsif type == "uri"
|
14
|
+
uri
|
15
|
+
else
|
16
|
+
raise "Unknown attribute type `#{type}`"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def time
|
21
|
+
arbitrary_time = Time.new(2012, 2, 1)
|
22
|
+
(arbitrary_time + @random.rand(0..500_000_000)).iso8601
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: make this more random with query string, optional anchor.
|
26
|
+
def uri
|
27
|
+
"http://example.com#{base_path}#{anchor}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def base_path
|
31
|
+
"/" + @random.rand(1..5).times.map { uuid }.join("/")
|
32
|
+
end
|
33
|
+
|
34
|
+
def govuk_subdomain_url
|
35
|
+
subdomain = @random.rand(2..4).times.map {
|
36
|
+
("a".."z").to_a.sample(@random.rand(3..8), random: @random).join
|
37
|
+
}.join(".")
|
38
|
+
"https://#{subdomain}.gov.uk#{base_path}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def string(minimum_chars = nil, maximum_chars = nil)
|
42
|
+
minimum_chars ||= 0
|
43
|
+
maximum_chars ||= 100
|
44
|
+
WORDS.sample(@random.rand(minimum_chars..maximum_chars), random: @random).join(" ")
|
45
|
+
end
|
46
|
+
|
47
|
+
def bool
|
48
|
+
@random.rand(2) == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def anchor
|
52
|
+
"##{hex}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def random_identifier(separator:)
|
56
|
+
WORDS.sample(@random.rand(1..10), random: @random)
|
57
|
+
.join("-")
|
58
|
+
.gsub(/[^a-z0-9\-_]+/i, "-")
|
59
|
+
.gsub("-", separator)
|
60
|
+
end
|
61
|
+
|
62
|
+
def uuid
|
63
|
+
# matches uuid regex e.g. e058aad7-ce86-5181-8801-4ddcb3c8f27c
|
64
|
+
# /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/
|
65
|
+
"#{hex(8)}-#{hex(4)}-1#{hex(3)}-a#{hex(3)}-#{hex(12)}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def hex(length = 10)
|
69
|
+
length.times.map { bool ? random_letter : random_number }.join("")
|
70
|
+
end
|
71
|
+
|
72
|
+
def string_for_regex(pattern)
|
73
|
+
case pattern.to_s
|
74
|
+
when "^(placeholder|placeholder_.+)$"
|
75
|
+
["placeholder", "placeholder_#{WORDS.sample(random: @random)}"].sample(random: @random)
|
76
|
+
when "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
|
77
|
+
uuid
|
78
|
+
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?$"
|
79
|
+
base_path
|
80
|
+
when "^[1-9][0-9]{3}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12][0-9]|3[0-1])$"
|
81
|
+
Date.today.iso8601
|
82
|
+
when "^[1-9][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])$"
|
83
|
+
Date.today.iso8601
|
84
|
+
when "^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$"
|
85
|
+
Time.now.strftime("%H:%m")
|
86
|
+
when "^#.+$"
|
87
|
+
anchor
|
88
|
+
when "[a-z-]"
|
89
|
+
random_identifier(separator: "-")
|
90
|
+
when "^[a-z_]+$"
|
91
|
+
random_identifier(separator: "_")
|
92
|
+
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?$"
|
93
|
+
base_path
|
94
|
+
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)+campaign\\.gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
|
95
|
+
govuk_subdomain_url
|
96
|
+
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)*gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
|
97
|
+
govuk_subdomain_url
|
98
|
+
when '[a-z0-9\-_]'
|
99
|
+
"#{hex}-#{hex}"
|
100
|
+
else
|
101
|
+
raise <<-DOC
|
102
|
+
Don't know how to generate random string for pattern #{pattern.inspect}
|
103
|
+
|
104
|
+
This propably means you've introduced a new regex in govuk-content-schemas.
|
105
|
+
Because it's very hard to generate a valid string from a regex alone,
|
106
|
+
we have to specify a method to generate random data for each regex in
|
107
|
+
the schemas.
|
108
|
+
|
109
|
+
To fix this:
|
110
|
+
|
111
|
+
- Add your regex to `lib/govuk_schemas/random.rb`
|
112
|
+
DOC
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def random_letter
|
119
|
+
letters = ("a".."f").to_a
|
120
|
+
letters[@random.rand(0..letters.count - 1)]
|
121
|
+
end
|
122
|
+
|
123
|
+
def random_number
|
124
|
+
numbers = ("0".."9").to_a
|
125
|
+
numbers[@random.rand(0..numbers.count - 1)]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require "govuk_schemas/
|
2
|
-
require "govuk_schemas/random_item_generator"
|
1
|
+
require "govuk_schemas/random_schema_generator"
|
3
2
|
require "json-schema"
|
4
3
|
require "json"
|
5
4
|
|
@@ -24,11 +23,17 @@ module GovukSchemas
|
|
24
23
|
# schema = GovukSchemas::Schema.find(frontend_schema: "detailed_guide")
|
25
24
|
# GovukSchemas::RandomExample.new(schema: schema).payload
|
26
25
|
#
|
26
|
+
# Example with seed (for consistent results):
|
27
|
+
#
|
28
|
+
# schema = GovukSchemas::Schema.find(frontend_schema: "detailed_guide")
|
29
|
+
# GovukSchemas::RandomExample.new(schema: schema, seed: 777).payload
|
30
|
+
# GovukSchemas::RandomExample.new(schema: schema, seed: 777).payload # returns same as above
|
31
|
+
#
|
27
32
|
# @param [Hash] schema A JSON schema.
|
28
33
|
# @return [GovukSchemas::RandomExample]
|
29
|
-
def initialize(schema:)
|
34
|
+
def initialize(schema:, seed: nil)
|
30
35
|
@schema = schema
|
31
|
-
@random_generator =
|
36
|
+
@random_generator = RandomSchemaGenerator.new(schema: schema, seed: seed)
|
32
37
|
end
|
33
38
|
|
34
39
|
# Returns a new `GovukSchemas::RandomExample` object.
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require "govuk_schemas/
|
1
|
+
require "govuk_schemas/random_content_generator"
|
2
2
|
|
3
3
|
module GovukSchemas
|
4
|
-
# The
|
4
|
+
# The RandomSchemaGenerator takes a JSON schema and outputs a random hash that
|
5
5
|
# is valid against said schema.
|
6
6
|
#
|
7
7
|
# The "randomness" here is quote relative, it's particularly tailored to the
|
@@ -9,9 +9,11 @@ module GovukSchemas
|
|
9
9
|
# hundred characters to keep the resulting items small.
|
10
10
|
#
|
11
11
|
# @private
|
12
|
-
class
|
13
|
-
def initialize(schema:)
|
12
|
+
class RandomSchemaGenerator
|
13
|
+
def initialize(schema:, seed: nil)
|
14
14
|
@schema = schema
|
15
|
+
@random = Random.new(seed || Random.new_seed)
|
16
|
+
@generator = RandomContentGenerator.new(random: @random)
|
15
17
|
end
|
16
18
|
|
17
19
|
def payload
|
@@ -24,45 +26,45 @@ module GovukSchemas
|
|
24
26
|
# TODO: #/definitions/nested_headers are recursively nested and can cause
|
25
27
|
# infinite loops. We need to add something that detects and prevents the
|
26
28
|
# loop. In the meantime return a valid value.
|
27
|
-
if props[
|
29
|
+
if props["$ref"] == "#/definitions/nested_headers"
|
28
30
|
return [{ "text" => "1", "level" => 1, "id" => "ABC" }]
|
29
31
|
end
|
30
32
|
|
31
33
|
# JSON schemas can have "pointers". We use this to extract defintions and
|
32
34
|
# reduce duplication. To make the schema easily parsable we inline the
|
33
35
|
# reference here.
|
34
|
-
if props[
|
35
|
-
props.merge!(lookup_json_pointer(props[
|
36
|
+
if props["$ref"]
|
37
|
+
props.merge!(lookup_json_pointer(props["$ref"]))
|
36
38
|
end
|
37
39
|
|
38
40
|
# Attributes with `enum` specified often omit the `type` from
|
39
41
|
# their definition. It's most likely a string.
|
40
|
-
type = props[
|
42
|
+
type = props["type"] || "string"
|
41
43
|
|
42
44
|
# Except when it has properties, because it's defintely an object then.
|
43
|
-
if props[
|
45
|
+
if props["properties"]
|
44
46
|
type = "object"
|
45
47
|
end
|
46
48
|
|
47
49
|
# Make sure that we choose a type when there are more than one specified.
|
48
|
-
type = Array(type).sample
|
50
|
+
type = Array(type).sample(random: @random)
|
49
51
|
|
50
|
-
if props[
|
51
|
-
generate_value(props[
|
52
|
-
elsif props[
|
52
|
+
if props["anyOf"]
|
53
|
+
generate_value(props["anyOf"].sample(random: @random))
|
54
|
+
elsif props["oneOf"] && type != "object"
|
53
55
|
# FIXME: Generating valid data for a `oneOf` schema is quite interesting.
|
54
56
|
# According to the JSON Schema spec a `oneOf` schema is only valid if
|
55
57
|
# the data is valid against *only one* of the clauses. To do this
|
56
58
|
# properly, we'd have to verify that the data generated below doesn't
|
57
59
|
# validate against the other schemas in `props['oneOf']`.
|
58
|
-
generate_value(props[
|
59
|
-
elsif props[
|
60
|
-
props[
|
60
|
+
generate_value(props["oneOf"].sample(random: @random))
|
61
|
+
elsif props["allOf"]
|
62
|
+
props["allOf"].each_with_object({}) do |subschema, hash|
|
61
63
|
val = generate_value(subschema)
|
62
64
|
hash.merge(val)
|
63
65
|
end
|
64
|
-
elsif props[
|
65
|
-
props[
|
66
|
+
elsif props["enum"]
|
67
|
+
props["enum"].sample(random: @random)
|
66
68
|
elsif type == "null"
|
67
69
|
nil
|
68
70
|
elsif type == "object"
|
@@ -70,11 +72,11 @@ module GovukSchemas
|
|
70
72
|
elsif type == "array"
|
71
73
|
generate_random_array(props)
|
72
74
|
elsif type == "boolean"
|
73
|
-
|
75
|
+
@generator.bool
|
74
76
|
elsif type == "integer"
|
75
|
-
min = props[
|
76
|
-
max = props[
|
77
|
-
rand(min..max)
|
77
|
+
min = props["minimum"] || 0
|
78
|
+
max = props["maximum"] || 10
|
79
|
+
@random.rand(min..max)
|
78
80
|
elsif type == "string"
|
79
81
|
generate_random_string(props)
|
80
82
|
else
|
@@ -85,56 +87,71 @@ module GovukSchemas
|
|
85
87
|
def generate_random_object(subschema)
|
86
88
|
document = {}
|
87
89
|
|
88
|
-
one_of_sample = subschema.fetch(
|
90
|
+
one_of_sample = subschema.fetch("oneOf", []).sample(random: @random) || {}
|
89
91
|
|
90
|
-
(subschema[
|
92
|
+
(subschema["properties"] || {}).each do |attribute_name, attribute_properties|
|
91
93
|
# TODO: When the schema contains `subschema['minProperties']` we always
|
92
94
|
# populate all of the keys in the hash. This isn't quite random, but I
|
93
95
|
# haven't found a nice way yet to ensure there's at least n elements in
|
94
96
|
# the hash.
|
95
|
-
should_generate_value =
|
96
|
-
|| subschema[
|
97
|
-
|| (one_of_sample[
|
98
|
-
|| (one_of_sample[
|
99
|
-
|| subschema[
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
97
|
+
should_generate_value = @generator.bool \
|
98
|
+
|| subschema["required"].to_a.include?(attribute_name) \
|
99
|
+
|| (one_of_sample["required"] || {}).to_a.include?(attribute_name) \
|
100
|
+
|| (one_of_sample["properties"] || {}).keys.include?(attribute_name) \
|
101
|
+
|| subschema["minProperties"] \
|
102
|
+
|
103
|
+
next unless should_generate_value
|
104
|
+
|
105
|
+
one_of_properties = (one_of_sample["properties"] || {})[attribute_name]
|
106
|
+
document[attribute_name] = if one_of_properties
|
107
|
+
generate_value(one_of_properties)
|
108
|
+
else
|
109
|
+
generate_value(attribute_properties)
|
110
|
+
end
|
109
111
|
end
|
110
112
|
|
111
113
|
document
|
112
114
|
end
|
113
115
|
|
114
116
|
def generate_random_array(props)
|
115
|
-
min = props[
|
116
|
-
max = props[
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
min = props["minItems"] || 0
|
118
|
+
max = props["maxItems"] || 10
|
119
|
+
unique = props["uniqueItems"] == true
|
120
|
+
num_items = @random.rand(min..max)
|
121
|
+
items = []
|
122
|
+
attempts = 0
|
123
|
+
max_attempts = num_items * 100
|
124
|
+
|
125
|
+
until items.length == num_items
|
120
126
|
# sometimes arrays don't have `items` specified, not sure if this is a bug
|
121
|
-
generate_value(props[
|
127
|
+
new_value = generate_value(props["items"] || {})
|
128
|
+
|
129
|
+
if unique && items.include?(new_value)
|
130
|
+
attempts += 1
|
131
|
+
raise "Failed to create a unique array item after #{max_attempts} attempts" if attempts >= max_attempts
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
attempts = 0
|
136
|
+
items << new_value
|
122
137
|
end
|
138
|
+
|
139
|
+
items
|
123
140
|
end
|
124
141
|
|
125
142
|
def generate_random_string(props)
|
126
143
|
if props["format"]
|
127
|
-
|
144
|
+
@generator.string_for_type(props["format"])
|
128
145
|
elsif props["pattern"]
|
129
|
-
|
146
|
+
@generator.string_for_regex(props["pattern"])
|
130
147
|
else
|
131
|
-
|
148
|
+
@generator.string(props["minLength"], props["maxLength"])
|
132
149
|
end
|
133
150
|
end
|
134
151
|
|
135
152
|
# Look up a "pointer" like "#/definitions/title" in the schema.
|
136
153
|
def lookup_json_pointer(ref)
|
137
|
-
elements = ref.split(
|
154
|
+
elements = ref.split("/")
|
138
155
|
elements.shift
|
139
156
|
@schema.dig(*elements) || raise("Definition `#{ref}` not found in the schema")
|
140
157
|
end
|
@@ -1,26 +1,16 @@
|
|
1
1
|
module GovukSchemas
|
2
2
|
module RSpecMatchers
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
RSpec::Matchers.define :be_valid_against_links_schema do |schema_name|
|
16
|
-
match do |item|
|
17
|
-
schema = Schema.find(links_schema: schema_name)
|
18
|
-
validator = JSON::Validator.fully_validate(schema, item)
|
19
|
-
validator.empty?
|
20
|
-
end
|
21
|
-
|
22
|
-
failure_message do |actual|
|
23
|
-
ValidationErrorMessage.new(schema_name, "links", actual).message
|
3
|
+
%w[links frontend publisher notification].each do |schema_type|
|
4
|
+
RSpec::Matchers.define "be_valid_against_#{schema_type}_schema".to_sym do |schema_name|
|
5
|
+
match do |item|
|
6
|
+
schema = Schema.find(Hash["#{schema_type}_schema".to_sym, schema_name])
|
7
|
+
validator = JSON::Validator.fully_validate(schema, item)
|
8
|
+
validator.empty?
|
9
|
+
end
|
10
|
+
|
11
|
+
failure_message do |actual|
|
12
|
+
ValidationErrorMessage.new(schema_name, "schema", actual).message
|
13
|
+
end
|
24
14
|
end
|
25
15
|
end
|
26
16
|
end
|
@@ -36,14 +26,14 @@ module GovukSchemas
|
|
36
26
|
end
|
37
27
|
|
38
28
|
def message
|
39
|
-
<<~
|
40
|
-
|
29
|
+
<<~DOC
|
30
|
+
expected the payload to be valid against the '#{schema_name}' schema:
|
41
31
|
|
42
|
-
|
32
|
+
#{formatted_payload}
|
43
33
|
|
44
|
-
|
45
|
-
|
46
|
-
|
34
|
+
Validation errors:
|
35
|
+
#{errors}
|
36
|
+
DOC
|
47
37
|
end
|
48
38
|
|
49
39
|
private
|
@@ -56,6 +46,7 @@ module GovukSchemas
|
|
56
46
|
|
57
47
|
def formatted_payload
|
58
48
|
return payload if payload.is_a?(String)
|
49
|
+
|
59
50
|
JSON.pretty_generate(payload)
|
60
51
|
end
|
61
52
|
|
data/lib/govuk_schemas/schema.rb
CHANGED
@@ -19,11 +19,10 @@ module GovukSchemas
|
|
19
19
|
#
|
20
20
|
# @param schema_type [String] The type: frontend, publisher, notification or links
|
21
21
|
# @return [Array<Hash>] List of JSON schemas as hashes
|
22
|
-
def self.all(schema_type:
|
22
|
+
def self.all(schema_type: "*")
|
23
23
|
schema_type = "publisher_v2" if schema_type == "publisher"
|
24
|
-
Dir.glob("#{GovukSchemas::CONTENT_SCHEMA_DIR}/dist/formats/*/#{schema_type}/*.json").
|
24
|
+
Dir.glob("#{GovukSchemas::CONTENT_SCHEMA_DIR}/dist/formats/*/#{schema_type}/*.json").each_with_object({}) do |file_path, hash|
|
25
25
|
hash[file_path] = JSON.parse(File.read(file_path))
|
26
|
-
hash
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: govuk_schemas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GOV.UK Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json-schema
|
@@ -25,61 +25,61 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.8.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: pry-byebug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.4'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.4'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name: govuk
|
70
|
+
name: rubocop-govuk
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '3.
|
75
|
+
version: '3.8'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '3.
|
82
|
+
version: '3.8'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: yard
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,6 +101,7 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
+
- ".github/dependabot.yml"
|
104
105
|
- ".gitignore"
|
105
106
|
- ".rspec"
|
106
107
|
- ".rubocop.yml"
|
@@ -117,12 +118,11 @@ files:
|
|
117
118
|
- lib/govuk_schemas.rb
|
118
119
|
- lib/govuk_schemas/document_types.rb
|
119
120
|
- lib/govuk_schemas/example.rb
|
120
|
-
- lib/govuk_schemas/
|
121
|
+
- lib/govuk_schemas/random_content_generator.rb
|
121
122
|
- lib/govuk_schemas/random_example.rb
|
122
|
-
- lib/govuk_schemas/
|
123
|
+
- lib/govuk_schemas/random_schema_generator.rb
|
123
124
|
- lib/govuk_schemas/rspec_matchers.rb
|
124
125
|
- lib/govuk_schemas/schema.rb
|
125
|
-
- lib/govuk_schemas/utils.rb
|
126
126
|
- lib/govuk_schemas/version.rb
|
127
127
|
homepage: https://github.com/alphagov/govuk_schemas_gem
|
128
128
|
licenses:
|
@@ -136,15 +136,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
136
136
|
requirements:
|
137
137
|
- - ">="
|
138
138
|
- !ruby/object:Gem::Version
|
139
|
-
version: 2.
|
139
|
+
version: '2.6'
|
140
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
142
|
- - ">="
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: '0'
|
145
145
|
requirements: []
|
146
|
-
|
147
|
-
rubygems_version: 2.5.1
|
146
|
+
rubygems_version: 3.1.4
|
148
147
|
signing_key:
|
149
148
|
specification_version: 4
|
150
149
|
summary: Gem to generate test data based on GOV.UK content schemas
|
data/lib/govuk_schemas/random.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
|
-
module GovukSchemas
|
4
|
-
# @private
|
5
|
-
module Random
|
6
|
-
class << self
|
7
|
-
WORDS = %w[Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut suscipit at mauris non bibendum. Ut ac massa est. Aenean tempor imperdiet leo vel interdum. Nam sagittis cursus sem ultricies scelerisque. Quisque porttitor risus vel risus finibus, eu sollicitudin nisl aliquet. Sed sed lectus ac dolor molestie interdum. Nam molestie pellentesque purus ac vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse non tempor eros. Mauris eu orci hendrerit, volutpat lorem in, tristique libero. Duis a nibh nibh.].freeze
|
8
|
-
|
9
|
-
def string_for_type(type)
|
10
|
-
if type == 'date-time'
|
11
|
-
time
|
12
|
-
elsif type == 'uri'
|
13
|
-
uri
|
14
|
-
else
|
15
|
-
raise "Unknown attribute type `#{type}`"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def time
|
20
|
-
seconds_ago = rand(10_000_000) - 5_000_000
|
21
|
-
(Time.now + seconds_ago).iso8601
|
22
|
-
end
|
23
|
-
|
24
|
-
# TODO: make this more random with query string, optional anchor.
|
25
|
-
def uri
|
26
|
-
"http://example.com#{base_path}#{anchor}"
|
27
|
-
end
|
28
|
-
|
29
|
-
def base_path
|
30
|
-
"/" + rand(1..5).times.map { SecureRandom.uuid }.join('/')
|
31
|
-
end
|
32
|
-
|
33
|
-
def govuk_subdomain_url
|
34
|
-
subdomain = rand(2..4).times.map {
|
35
|
-
('a'..'z').to_a.sample(rand(3..8)).join
|
36
|
-
}.join('.')
|
37
|
-
"https://#{subdomain}.gov.uk#{base_path}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def string(minimum_chars = nil, maximum_chars = nil)
|
41
|
-
minimum_chars = minimum_chars || 0
|
42
|
-
maximum_chars = maximum_chars || 100
|
43
|
-
WORDS.sample(rand(minimum_chars..maximum_chars)).join(' ')
|
44
|
-
end
|
45
|
-
|
46
|
-
def bool
|
47
|
-
rand(2) == 1
|
48
|
-
end
|
49
|
-
|
50
|
-
def anchor
|
51
|
-
"##{SecureRandom.hex}"
|
52
|
-
end
|
53
|
-
|
54
|
-
def random_identifier(separator:)
|
55
|
-
Utils.parameterize(WORDS.sample(rand(1..10)).join('-')).gsub('-', separator)
|
56
|
-
end
|
57
|
-
|
58
|
-
def string_for_regex(pattern)
|
59
|
-
case pattern.to_s
|
60
|
-
when '^(placeholder|placeholder_.+)$'
|
61
|
-
['placeholder', "placeholder_#{WORDS.sample}"].sample
|
62
|
-
when '^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$'
|
63
|
-
SecureRandom.uuid
|
64
|
-
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?$"
|
65
|
-
base_path
|
66
|
-
when "^[1-9][0-9]{3}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12][0-9]|3[0-1])$"
|
67
|
-
Date.today.iso8601
|
68
|
-
when "^[1-9][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])$"
|
69
|
-
Date.today.iso8601
|
70
|
-
when "^#.+$"
|
71
|
-
anchor
|
72
|
-
when "[a-z-]"
|
73
|
-
random_identifier(separator: '-')
|
74
|
-
when "^[a-z_]+$"
|
75
|
-
random_identifier(separator: '_')
|
76
|
-
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?$"
|
77
|
-
base_path
|
78
|
-
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)+campaign\\.gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
|
79
|
-
govuk_subdomain_url
|
80
|
-
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)*gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
|
81
|
-
govuk_subdomain_url
|
82
|
-
when '[a-z0-9\-_]'
|
83
|
-
"#{SecureRandom.hex}-#{SecureRandom.hex}"
|
84
|
-
else
|
85
|
-
raise <<-doc
|
86
|
-
Don't know how to generate random string for pattern #{pattern.inspect}
|
87
|
-
|
88
|
-
This propably means you've introduced a new regex in govuk-content-schemas.
|
89
|
-
Because it's very hard to generate a valid string from a regex alone,
|
90
|
-
we have to specify a method to generate random data for each regex in
|
91
|
-
the schemas.
|
92
|
-
|
93
|
-
To fix this:
|
94
|
-
|
95
|
-
- Add your regex to `lib/govuk_schemas/random.rb`
|
96
|
-
doc
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
data/lib/govuk_schemas/utils.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module GovukSchemas
|
2
|
-
# @private
|
3
|
-
module Utils
|
4
|
-
def self.stringify_keys(hash)
|
5
|
-
new_hash = {}
|
6
|
-
hash.each do |k, v|
|
7
|
-
new_hash[k.to_s] = v
|
8
|
-
end
|
9
|
-
new_hash
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.parameterize(string)
|
13
|
-
string.gsub(/[^a-z0-9\-_]+/i, '-')
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|