strain 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +17 -0
  6. data/Guardfile +72 -0
  7. data/LICENSE +20 -0
  8. data/README.md +74 -0
  9. data/Rakefile +26 -0
  10. data/app/assets/javascripts/strain/jsonapi/articles.js +2 -0
  11. data/app/assets/javascripts/strain/jsonapi/variants.js +2 -0
  12. data/app/assets/stylesheets/strain/jsonapi/articles.css +4 -0
  13. data/app/assets/stylesheets/strain/jsonapi/variants.css +4 -0
  14. data/app/controllers/strain/application_controller.rb +5 -0
  15. data/app/controllers/strain/jsonapi/articles_controller.rb +6 -0
  16. data/app/controllers/strain/jsonapi/jsonapi_controller.rb +10 -0
  17. data/app/controllers/strain/jsonapi/variants_controller.rb +7 -0
  18. data/app/helpers/strain/jsonapi/articles_helper.rb +4 -0
  19. data/app/helpers/strain/jsonapi/variants_helper.rb +4 -0
  20. data/app/models/strain/article.rb +37 -0
  21. data/app/models/strain/variant.rb +32 -0
  22. data/app/resources/strain/jsonapi/article_resource.rb +21 -0
  23. data/app/resources/strain/jsonapi/variant_resource.rb +16 -0
  24. data/bin/_guard-core +16 -0
  25. data/bin/guard +16 -0
  26. data/bin/rails +12 -0
  27. data/bin/rake +16 -0
  28. data/bin/rspec +16 -0
  29. data/config/initializers/jsonapi_resources.rb +4 -0
  30. data/config/routes.rb +8 -0
  31. data/db/migrate/20151103060035_enable_uuid.rb +5 -0
  32. data/db/migrate/20151103060224_create_strain_variants.rb +11 -0
  33. data/db/migrate/20151104200908_create_strain_articles.rb +14 -0
  34. data/lib/json_schema/draft_4_meta_schema.json +150 -0
  35. data/lib/strain/engine.rb +21 -0
  36. data/lib/strain/schema_tools/defaults.rb +53 -0
  37. data/lib/strain/version.rb +3 -0
  38. data/lib/strain.rb +17 -0
  39. data/lib/tasks/strain_tasks.rake +4 -0
  40. data/spec/factories/strain_articles.rb +15 -0
  41. data/spec/factories/strain_variants.rb +34 -0
  42. data/spec/lib/strain/schema_tools/defaults_spec.rb +234 -0
  43. data/spec/models/strain/article_spec.rb +72 -0
  44. data/spec/models/strain/variant_spec.rb +42 -0
  45. data/spec/rails_helper.rb +59 -0
  46. data/spec/requests/strain/jsonapi/articles_spec.rb +220 -0
  47. data/spec/requests/strain/jsonapi/variants_spec.rb +159 -0
  48. data/spec/spec_helper.rb +99 -0
  49. data/spec/test_app/README.rdoc +28 -0
  50. data/spec/test_app/Rakefile +6 -0
  51. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  52. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  53. data/spec/test_app/app/controllers/application_controller.rb +5 -0
  54. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  55. data/spec/test_app/app/models/tweet.rb +4 -0
  56. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  57. data/spec/test_app/bin/bundle +3 -0
  58. data/spec/test_app/bin/rails +4 -0
  59. data/spec/test_app/bin/rake +4 -0
  60. data/spec/test_app/bin/setup +29 -0
  61. data/spec/test_app/config/application.rb +32 -0
  62. data/spec/test_app/config/boot.rb +5 -0
  63. data/spec/test_app/config/database.yml +85 -0
  64. data/spec/test_app/config/environment.rb +5 -0
  65. data/spec/test_app/config/environments/development.rb +41 -0
  66. data/spec/test_app/config/environments/production.rb +79 -0
  67. data/spec/test_app/config/environments/test.rb +42 -0
  68. data/spec/test_app/config/initializers/assets.rb +11 -0
  69. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  70. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  71. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  72. data/spec/test_app/config/initializers/inflections.rb +16 -0
  73. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  74. data/spec/test_app/config/initializers/session_store.rb +3 -0
  75. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  76. data/spec/test_app/config/locales/en.yml +23 -0
  77. data/spec/test_app/config/routes.rb +4 -0
  78. data/spec/test_app/config/secrets.yml +22 -0
  79. data/spec/test_app/config.ru +4 -0
  80. data/spec/test_app/db/schema.rb +42 -0
  81. data/spec/test_app/log/development.log +2854 -0
  82. data/spec/test_app/log/test.log +40082 -0
  83. data/spec/test_app/public/404.html +67 -0
  84. data/spec/test_app/public/422.html +67 -0
  85. data/spec/test_app/public/500.html +66 -0
  86. data/spec/test_app/public/favicon.ico +0 -0
  87. 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
@@ -0,0 +1,3 @@
1
+ module Strain
2
+ VERSION = "0.0.1"
3
+ 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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :strain do
3
+ # # Task goes here
4
+ # 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