strain 0.0.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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +17 -0
- data/Guardfile +72 -0
- data/LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +26 -0
- data/app/assets/javascripts/strain/jsonapi/articles.js +2 -0
- data/app/assets/javascripts/strain/jsonapi/variants.js +2 -0
- data/app/assets/stylesheets/strain/jsonapi/articles.css +4 -0
- data/app/assets/stylesheets/strain/jsonapi/variants.css +4 -0
- data/app/controllers/strain/application_controller.rb +5 -0
- data/app/controllers/strain/jsonapi/articles_controller.rb +6 -0
- data/app/controllers/strain/jsonapi/jsonapi_controller.rb +10 -0
- data/app/controllers/strain/jsonapi/variants_controller.rb +7 -0
- data/app/helpers/strain/jsonapi/articles_helper.rb +4 -0
- data/app/helpers/strain/jsonapi/variants_helper.rb +4 -0
- data/app/models/strain/article.rb +37 -0
- data/app/models/strain/variant.rb +32 -0
- data/app/resources/strain/jsonapi/article_resource.rb +21 -0
- data/app/resources/strain/jsonapi/variant_resource.rb +16 -0
- data/bin/_guard-core +16 -0
- data/bin/guard +16 -0
- data/bin/rails +12 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/config/initializers/jsonapi_resources.rb +4 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20151103060035_enable_uuid.rb +5 -0
- data/db/migrate/20151103060224_create_strain_variants.rb +11 -0
- data/db/migrate/20151104200908_create_strain_articles.rb +14 -0
- data/lib/json_schema/draft_4_meta_schema.json +150 -0
- data/lib/strain/engine.rb +21 -0
- data/lib/strain/schema_tools/defaults.rb +53 -0
- data/lib/strain/version.rb +3 -0
- data/lib/strain.rb +17 -0
- data/lib/tasks/strain_tasks.rake +4 -0
- data/spec/factories/strain_articles.rb +15 -0
- data/spec/factories/strain_variants.rb +34 -0
- data/spec/lib/strain/schema_tools/defaults_spec.rb +234 -0
- data/spec/models/strain/article_spec.rb +72 -0
- data/spec/models/strain/variant_spec.rb +42 -0
- data/spec/rails_helper.rb +59 -0
- data/spec/requests/strain/jsonapi/articles_spec.rb +220 -0
- data/spec/requests/strain/jsonapi/variants_spec.rb +159 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/test_app/README.rdoc +28 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/javascripts/application.js +13 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/controllers/application_controller.rb +5 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/models/tweet.rb +4 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/bin/setup +29 -0
- data/spec/test_app/config/application.rb +32 -0
- data/spec/test_app/config/boot.rb +5 -0
- data/spec/test_app/config/database.yml +85 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +41 -0
- data/spec/test_app/config/environments/production.rb +79 -0
- data/spec/test_app/config/environments/test.rb +42 -0
- data/spec/test_app/config/initializers/assets.rb +11 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/session_store.rb +3 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +23 -0
- data/spec/test_app/config/routes.rb +4 -0
- data/spec/test_app/config/secrets.yml +22 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/db/schema.rb +42 -0
- data/spec/test_app/log/development.log +2854 -0
- data/spec/test_app/log/test.log +40082 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/favicon.ico +0 -0
- metadata +262 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
{
|
2
|
+
"id": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
4
|
+
"description": "Core schema meta-schema",
|
5
|
+
"definitions": {
|
6
|
+
"schemaArray": {
|
7
|
+
"type": "array",
|
8
|
+
"minItems": 1,
|
9
|
+
"items": { "$ref": "#" }
|
10
|
+
},
|
11
|
+
"positiveInteger": {
|
12
|
+
"type": "integer",
|
13
|
+
"minimum": 0
|
14
|
+
},
|
15
|
+
"positiveIntegerDefault0": {
|
16
|
+
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
17
|
+
},
|
18
|
+
"simpleTypes": {
|
19
|
+
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
20
|
+
},
|
21
|
+
"stringArray": {
|
22
|
+
"type": "array",
|
23
|
+
"items": { "type": "string" },
|
24
|
+
"minItems": 1,
|
25
|
+
"uniqueItems": true
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"type": "object",
|
29
|
+
"properties": {
|
30
|
+
"id": {
|
31
|
+
"type": "string",
|
32
|
+
"format": "uri"
|
33
|
+
},
|
34
|
+
"$schema": {
|
35
|
+
"type": "string",
|
36
|
+
"format": "uri"
|
37
|
+
},
|
38
|
+
"title": {
|
39
|
+
"type": "string"
|
40
|
+
},
|
41
|
+
"description": {
|
42
|
+
"type": "string"
|
43
|
+
},
|
44
|
+
"default": {},
|
45
|
+
"multipleOf": {
|
46
|
+
"type": "number",
|
47
|
+
"minimum": 0,
|
48
|
+
"exclusiveMinimum": true
|
49
|
+
},
|
50
|
+
"maximum": {
|
51
|
+
"type": "number"
|
52
|
+
},
|
53
|
+
"exclusiveMaximum": {
|
54
|
+
"type": "boolean",
|
55
|
+
"default": false
|
56
|
+
},
|
57
|
+
"minimum": {
|
58
|
+
"type": "number"
|
59
|
+
},
|
60
|
+
"exclusiveMinimum": {
|
61
|
+
"type": "boolean",
|
62
|
+
"default": false
|
63
|
+
},
|
64
|
+
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
65
|
+
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
66
|
+
"pattern": {
|
67
|
+
"type": "string",
|
68
|
+
"format": "regex"
|
69
|
+
},
|
70
|
+
"additionalItems": {
|
71
|
+
"anyOf": [
|
72
|
+
{ "type": "boolean" },
|
73
|
+
{ "$ref": "#" }
|
74
|
+
],
|
75
|
+
"default": {}
|
76
|
+
},
|
77
|
+
"items": {
|
78
|
+
"anyOf": [
|
79
|
+
{ "$ref": "#" },
|
80
|
+
{ "$ref": "#/definitions/schemaArray" }
|
81
|
+
],
|
82
|
+
"default": {}
|
83
|
+
},
|
84
|
+
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
85
|
+
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
86
|
+
"uniqueItems": {
|
87
|
+
"type": "boolean",
|
88
|
+
"default": false
|
89
|
+
},
|
90
|
+
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
91
|
+
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
92
|
+
"required": { "$ref": "#/definitions/stringArray" },
|
93
|
+
"additionalProperties": {
|
94
|
+
"anyOf": [
|
95
|
+
{ "type": "boolean" },
|
96
|
+
{ "$ref": "#" }
|
97
|
+
],
|
98
|
+
"default": {}
|
99
|
+
},
|
100
|
+
"definitions": {
|
101
|
+
"type": "object",
|
102
|
+
"additionalProperties": { "$ref": "#" },
|
103
|
+
"default": {}
|
104
|
+
},
|
105
|
+
"properties": {
|
106
|
+
"type": "object",
|
107
|
+
"additionalProperties": { "$ref": "#" },
|
108
|
+
"default": {}
|
109
|
+
},
|
110
|
+
"patternProperties": {
|
111
|
+
"type": "object",
|
112
|
+
"additionalProperties": { "$ref": "#" },
|
113
|
+
"default": {}
|
114
|
+
},
|
115
|
+
"dependencies": {
|
116
|
+
"type": "object",
|
117
|
+
"additionalProperties": {
|
118
|
+
"anyOf": [
|
119
|
+
{ "$ref": "#" },
|
120
|
+
{ "$ref": "#/definitions/stringArray" }
|
121
|
+
]
|
122
|
+
}
|
123
|
+
},
|
124
|
+
"enum": {
|
125
|
+
"type": "array",
|
126
|
+
"minItems": 1,
|
127
|
+
"uniqueItems": true
|
128
|
+
},
|
129
|
+
"type": {
|
130
|
+
"anyOf": [
|
131
|
+
{ "$ref": "#/definitions/simpleTypes" },
|
132
|
+
{
|
133
|
+
"type": "array",
|
134
|
+
"items": { "$ref": "#/definitions/simpleTypes" },
|
135
|
+
"minItems": 1,
|
136
|
+
"uniqueItems": true
|
137
|
+
}
|
138
|
+
]
|
139
|
+
},
|
140
|
+
"allOf": { "$ref": "#/definitions/schemaArray" },
|
141
|
+
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
142
|
+
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
143
|
+
"not": { "$ref": "#" }
|
144
|
+
},
|
145
|
+
"dependencies": {
|
146
|
+
"exclusiveMaximum": [ "maximum" ],
|
147
|
+
"exclusiveMinimum": [ "minimum" ]
|
148
|
+
},
|
149
|
+
"default": {}
|
150
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Strain
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
require "json-schema"
|
5
|
+
require "jsonapi-resources"
|
6
|
+
|
7
|
+
isolate_namespace Strain
|
8
|
+
|
9
|
+
config.autoload_paths += Dir["#{config.root}/lib/**/"]
|
10
|
+
|
11
|
+
config.generators do |g|
|
12
|
+
g.test_framework :rspec,
|
13
|
+
view_specs: false,
|
14
|
+
helper_specs: false,
|
15
|
+
routing_specs: true,
|
16
|
+
controller_specs: true,
|
17
|
+
request_specs: true
|
18
|
+
g.fixture_replacement :factory_girl, dir: "spec/factories"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Strain
|
2
|
+
module SchemaTools
|
3
|
+
class Defaults
|
4
|
+
|
5
|
+
def initialize(schema_hash, data_hash)
|
6
|
+
@schema = schema_hash
|
7
|
+
@data = data_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_values
|
11
|
+
raise "Invalid schema." unless @schema["type"] == "object" && @schema.has_key?("properties")
|
12
|
+
property_defaults(@schema["properties"])
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_with_defaults
|
16
|
+
merge_defaults(@data, default_values)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def property_defaults(properties_hash)
|
22
|
+
properties_hash.each_with_object({}) do | (prop_name, prop_schema), memo |
|
23
|
+
if prop_schema["type"] === "object" &&
|
24
|
+
prop_schema.has_key?("default")
|
25
|
+
memo[prop_name] = prop_schema["default"]
|
26
|
+
end
|
27
|
+
|
28
|
+
if prop_schema["type"] === "object" &&
|
29
|
+
prop_schema.has_key?("properties")
|
30
|
+
extracted_defaults = property_defaults(prop_schema["properties"])
|
31
|
+
memo[prop_name] = extracted_defaults unless extracted_defaults.empty?
|
32
|
+
elsif prop_schema.has_key?("default")
|
33
|
+
memo[prop_name] = prop_schema["default"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge_defaults(data, defaults)
|
39
|
+
data.merge(defaults) do | key, data_value, default_value |
|
40
|
+
if data_value.is_a? Hash
|
41
|
+
merge_defaults(data_value, default_value)
|
42
|
+
elsif [nil, "", []].include?(data_value)
|
43
|
+
default_value
|
44
|
+
else
|
45
|
+
data_value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/strain.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "strain/engine"
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
|
5
|
+
JSONAPI::Exceptions::ValidationErrors.module_eval do
|
6
|
+
def json_api_error(attr_key, message)
|
7
|
+
a = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR,
|
8
|
+
status: :unprocessable_entity,
|
9
|
+
title: message,
|
10
|
+
detail: "#{format_key(attr_key)} - #{message}",
|
11
|
+
source: { pointer: pointer(attr_key) })
|
12
|
+
# binding.pry
|
13
|
+
a
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :article, :class => 'Strain::Article' do
|
4
|
+
body_hash = {
|
5
|
+
"summary": "this is a test article",
|
6
|
+
"main": "This is the main article body."
|
7
|
+
}
|
8
|
+
|
9
|
+
body body_hash
|
10
|
+
slug "test-article"
|
11
|
+
title "Test Article"
|
12
|
+
variant
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :variant, class: Strain::Variant do
|
3
|
+
schema_hash = {
|
4
|
+
"type": "object",
|
5
|
+
"properties": {
|
6
|
+
"summary": {"type": "string"},
|
7
|
+
"main": {"type": "string"}
|
8
|
+
},
|
9
|
+
"additionalProperties": false
|
10
|
+
}
|
11
|
+
name "test"
|
12
|
+
schema schema_hash
|
13
|
+
|
14
|
+
trait :nested_schema do
|
15
|
+
schema_hash = {
|
16
|
+
"type": "object",
|
17
|
+
"additionalProperties": false,
|
18
|
+
"properties": {
|
19
|
+
"summary": {"type": "string"},
|
20
|
+
"main": {
|
21
|
+
"type": "object",
|
22
|
+
"additionalProperties": false,
|
23
|
+
"properties": {
|
24
|
+
"section_title": {"type": "string"},
|
25
|
+
"section_text": {"type": "string"}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
schema schema_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'strain/schema_tools/defaults.rb'
|
3
|
+
|
4
|
+
module Strain::SchemaTools
|
5
|
+
RSpec.describe Defaults do
|
6
|
+
subject(:d) { Defaults.new(schema, data) }
|
7
|
+
let(:schema) do
|
8
|
+
{ "type" => "object", "properties" => {
|
9
|
+
"title" => {"type" => "string", "default" => "default title"},
|
10
|
+
"summary" => {"type" => "string"},
|
11
|
+
"author" => {
|
12
|
+
"type" => "object", "properties" => {
|
13
|
+
"name" => {"type" => "string", "default" => "default author"},
|
14
|
+
"date" => {"type" => "integer", "default" => 2005}
|
15
|
+
} } } }
|
16
|
+
end
|
17
|
+
let(:data) do
|
18
|
+
{ "title" => nil,
|
19
|
+
"summary" => "test summary",
|
20
|
+
"author" => {
|
21
|
+
"name" => "Jane Author",
|
22
|
+
"date" => nil
|
23
|
+
} }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#default_values" do
|
27
|
+
subject(:default_values) {d.default_values}
|
28
|
+
|
29
|
+
context "with a simple schema" do
|
30
|
+
let(:schema) do
|
31
|
+
{ "type" => "object", "properties" => {
|
32
|
+
"title" => {"type" => "string", "default" => "default title"}
|
33
|
+
} }
|
34
|
+
end
|
35
|
+
it "returns a hash with all declared defaults" do
|
36
|
+
expect(default_values).to eq({"title" => "default title"})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with a typical schema" do
|
41
|
+
it "returns a hash with all declared defaults" do
|
42
|
+
expect(default_values).to eq(
|
43
|
+
{"title"=>"default title", "author"=>{"name"=>"default author", "date"=>2005}}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with a nested schema" do
|
49
|
+
let(:schema) do
|
50
|
+
{
|
51
|
+
"type" => "object", "properties" => {
|
52
|
+
"title" => {"type" => "string", "default" => "default title"},
|
53
|
+
"summary" => {"type" => "string"},
|
54
|
+
"author" => {
|
55
|
+
"type" => "object", "properties" => {
|
56
|
+
"name" => {"type" => "string", "default" => "default author"},
|
57
|
+
"date" => {"type" => "integer", "default" => 2005}
|
58
|
+
} } } }
|
59
|
+
end
|
60
|
+
it "returns a hash with all declared defaults" do
|
61
|
+
expect(default_values).to eq({
|
62
|
+
"title" => "default title",
|
63
|
+
"author" => {
|
64
|
+
"name" => "default author",
|
65
|
+
"date" => 2005
|
66
|
+
} })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with array defaults" do
|
71
|
+
let(:schema) do
|
72
|
+
{ "type" => "object", "properties" => {
|
73
|
+
"numbers" => {"type" => "array", "default" => "[1, 2, 3]"}
|
74
|
+
} }
|
75
|
+
end
|
76
|
+
it "returns a hash with all declared defaults" do
|
77
|
+
expect(default_values).to eq({"numbers" => "[1, 2, 3]"})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with non-conflicting object defaults" do
|
82
|
+
let(:schema) do
|
83
|
+
{ "type" => "object", "properties" => {
|
84
|
+
"obj_with_defaults" => {
|
85
|
+
"type" => "object",
|
86
|
+
"default" => {"inner_prop" => "test"},
|
87
|
+
"properties" => {"inner_prop" => { "type": "string" }}
|
88
|
+
} } }
|
89
|
+
end
|
90
|
+
it "returns a hash with all declared defaults" do
|
91
|
+
expect(default_values).to eq(
|
92
|
+
{"obj_with_defaults" => {"inner_prop" => "test"}}
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with conflicting object defaults" do
|
98
|
+
let(:schema) do
|
99
|
+
{ "type" => "object", "properties" => {
|
100
|
+
"obj_with_defaults" => {
|
101
|
+
"type" => "object",
|
102
|
+
"default" => {"inner_prop" => "outer_default"},
|
103
|
+
"properties" => {
|
104
|
+
"inner_prop" => {"type" => "string", "default" => "inner_default"}
|
105
|
+
} } } }
|
106
|
+
end
|
107
|
+
it "prioritizes inner defaults" do
|
108
|
+
expect(default_values).to eq(
|
109
|
+
{"obj_with_defaults" => {"inner_prop" => "inner_default"}}
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with no properties on an object and no defaults" do
|
115
|
+
let(:schema) do
|
116
|
+
{ "type" => "object", "properties" => {
|
117
|
+
"obj_with_defaults" => {
|
118
|
+
"type" => "object"
|
119
|
+
} } }
|
120
|
+
end
|
121
|
+
it "returns a blank hash" do
|
122
|
+
expect(default_values).to eq({})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with no properties on an object and defaults" do
|
127
|
+
let(:schema) do
|
128
|
+
{ "type" => "object", "properties" => {
|
129
|
+
"obj_with_defaults" => {
|
130
|
+
"type" => "object",
|
131
|
+
"default" => {"inner_prop" => "test"}
|
132
|
+
} } }
|
133
|
+
end
|
134
|
+
it "returns a hash with defaults set" do
|
135
|
+
expect(default_values).to eq({"obj_with_defaults" => {"inner_prop" => "test"}})
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#data_with_defaults" do
|
141
|
+
subject(:data_with_defaults) {d.data_with_defaults}
|
142
|
+
|
143
|
+
context "with some missing data and basic schema" do
|
144
|
+
it "merges defaults with the data" do
|
145
|
+
expect(data_with_defaults).to eq({
|
146
|
+
"title" => "default title",
|
147
|
+
"summary" => "test summary",
|
148
|
+
"author" => {
|
149
|
+
"name" => "Jane Author",
|
150
|
+
"date" => 2005
|
151
|
+
} })
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "with blank strings" do
|
156
|
+
let(:data) do
|
157
|
+
{ "title" => "",
|
158
|
+
"summary" => "",
|
159
|
+
"author" => ""
|
160
|
+
}
|
161
|
+
end
|
162
|
+
it "replaces blank strings with defaults" do
|
163
|
+
expect(data_with_defaults).to eq({
|
164
|
+
"title" => "default title",
|
165
|
+
"summary" => "",
|
166
|
+
"author" => {
|
167
|
+
"name" => "default author",
|
168
|
+
"date" => 2005
|
169
|
+
} })
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with blank arrays" do
|
174
|
+
let(:data) do
|
175
|
+
{"numbers" => []}
|
176
|
+
end
|
177
|
+
let(:schema) do
|
178
|
+
{ "type" => "object",
|
179
|
+
"properties" => {
|
180
|
+
"numbers" => {"type" => "array", "default" => [1,2,3]}
|
181
|
+
} }
|
182
|
+
end
|
183
|
+
it "replaces blank arrays with defaults" do
|
184
|
+
expect(data_with_defaults).to eq({
|
185
|
+
"numbers" => [1,2,3]
|
186
|
+
})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "with blank arrays containing objects" do
|
191
|
+
let(:data) do
|
192
|
+
{"tags" => []}
|
193
|
+
end
|
194
|
+
let(:schema) do
|
195
|
+
{ "type" => "object",
|
196
|
+
"properties" => {
|
197
|
+
"tags" => {
|
198
|
+
"type" => "array",
|
199
|
+
"items" => {"type" => "string"},
|
200
|
+
"default" => [{"tag1" => "red"}, {"tag2"=>"green"}]
|
201
|
+
} } }
|
202
|
+
end
|
203
|
+
it "replaces blank arrays with defaults" do
|
204
|
+
expect(data_with_defaults).to eq({
|
205
|
+
"tags" => [{"tag1" => "red"}, {"tag2"=>"green"}]
|
206
|
+
})
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "with deeply nested objects" do
|
211
|
+
let(:data) do
|
212
|
+
{"second_level" => {} }
|
213
|
+
end
|
214
|
+
let(:schema) do
|
215
|
+
{ "type" => "object",
|
216
|
+
"properties" => {
|
217
|
+
"second_level" => {
|
218
|
+
"type" => "object",
|
219
|
+
"properties" => {
|
220
|
+
"third_level" => {
|
221
|
+
"type" => "object",
|
222
|
+
"default" => {"deep_property" => "test"}
|
223
|
+
} } } } }
|
224
|
+
end
|
225
|
+
it "merges defaults" do
|
226
|
+
expect(data_with_defaults).to eq({
|
227
|
+
"second_level"=>{"third_level"=>{"deep_property"=>"test"}}
|
228
|
+
})
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
RSpec.describe Article, type: :model do
|
5
|
+
|
6
|
+
it "has a valid factory" do
|
7
|
+
expect(build(:article)).to be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is invalid without a slug" do
|
11
|
+
expect(build(:article, slug: nil)).to be_invalid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is invalid with a non-URL-safe slug" do
|
15
|
+
expect(build(:article, slug: "invalid slug")).to be_invalid
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is invalid without a variant" do
|
19
|
+
expect(build(:article, variant: nil)).to be_invalid
|
20
|
+
end
|
21
|
+
|
22
|
+
it "is invalid without a body" do
|
23
|
+
expect(build(:article, body: nil)).to be_invalid
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is invalid without a title" do
|
27
|
+
expect(build(:article, title: nil)).to be_invalid
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises a database error with a nonunique slug" do
|
31
|
+
a = create(:article)
|
32
|
+
b = build(:article, slug: a.slug, variant: a.variant)
|
33
|
+
expect {b.save(validate: false)}.to raise_error(ActiveRecord::RecordNotUnique)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is invalid with a nonunique slug" do
|
37
|
+
a = create(:article)
|
38
|
+
b = build(:article, slug: a.slug, variant: a.variant)
|
39
|
+
expect(b).to be_invalid
|
40
|
+
end
|
41
|
+
|
42
|
+
it "is valid when compliant with its Variant" do
|
43
|
+
v = create(:variant)
|
44
|
+
a = build(:article, variant: v, body: {"summary": "test", "main": "test"})
|
45
|
+
expect(a).to be_valid
|
46
|
+
end
|
47
|
+
|
48
|
+
it "is invalid when not compliant with its Variant" do
|
49
|
+
v = create(:variant)
|
50
|
+
a = build(:article, variant: v, body: {"extra_property": "test", "main": "test"})
|
51
|
+
expect(a).to be_invalid
|
52
|
+
end
|
53
|
+
|
54
|
+
it "fills in schema-defined defaults" do
|
55
|
+
v = build(:variant, schema: {
|
56
|
+
"type": "object",
|
57
|
+
"properties": {
|
58
|
+
"title": {
|
59
|
+
"type": "string",
|
60
|
+
"default": "default Title"
|
61
|
+
}
|
62
|
+
}
|
63
|
+
})
|
64
|
+
a = create(:article, variant: v, body:{})
|
65
|
+
expect(a.body["title"]).to eq("default Title")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "provides detailed errors when invalid according to JSON schema"
|
69
|
+
it "enforces key format? - hyphenated, underscores, camelcase?"
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
RSpec.describe Variant, type: :model do
|
5
|
+
|
6
|
+
it "has a valid factory" do
|
7
|
+
expect(build(:variant)).to be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is invalid without a name" do
|
11
|
+
expect(build(:variant, name: nil)).to be_invalid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is invalid without a Schema" do
|
15
|
+
expect(build(:variant, schema: nil)).to be_invalid
|
16
|
+
expect(build(:variant, schema: {})).to be_invalid
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is invalid with an invalid JSON Schema" do
|
20
|
+
expect(build(:variant, schema: {"type": "nonsense"})).to be_invalid
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises a database error with a nonunique name" do
|
24
|
+
a = create(:variant)
|
25
|
+
b = build(:variant, name: a.name)
|
26
|
+
expect {b.save(validate: false)}.to raise_error(ActiveRecord::RecordNotUnique)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is invalid with a nonunique name" do
|
30
|
+
a = create(:variant)
|
31
|
+
b = build(:variant, name: a.name)
|
32
|
+
expect(b).to be_invalid
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is invalid unless root schema type is object" do
|
36
|
+
a = build(:variant, schema: {"type": "string"})
|
37
|
+
expect(a).to be_invalid
|
38
|
+
expect(a.errors.messages[:schema]).to include("must have an object as root element.")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|