govuk_content_models 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/.gitignore +17 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +14 -0
  4. data/CONTRIBUTING.md +22 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +5 -0
  8. data/Rakefile +46 -0
  9. data/app/models/action.rb +60 -0
  10. data/app/models/answer_edition.rb +13 -0
  11. data/app/models/artefact.rb +341 -0
  12. data/app/models/artefact_action.rb +27 -0
  13. data/app/models/artefact_external_link.rb +15 -0
  14. data/app/models/business_support/business_size.rb +14 -0
  15. data/app/models/business_support/business_type.rb +14 -0
  16. data/app/models/business_support/location.rb +14 -0
  17. data/app/models/business_support/purpose.rb +14 -0
  18. data/app/models/business_support/sector.rb +14 -0
  19. data/app/models/business_support/stage.rb +14 -0
  20. data/app/models/business_support/support_type.rb +14 -0
  21. data/app/models/business_support_edition.rb +69 -0
  22. data/app/models/campaign_edition.rb +72 -0
  23. data/app/models/completed_transaction_edition.rb +14 -0
  24. data/app/models/curated_list.rb +32 -0
  25. data/app/models/edition.rb +286 -0
  26. data/app/models/expectant.rb +21 -0
  27. data/app/models/expectation.rb +12 -0
  28. data/app/models/guide_edition.rb +19 -0
  29. data/app/models/help_page_edition.rb +13 -0
  30. data/app/models/licence_edition.rb +35 -0
  31. data/app/models/local_authority.rb +58 -0
  32. data/app/models/local_interaction.rb +20 -0
  33. data/app/models/local_service.rb +49 -0
  34. data/app/models/local_transaction_edition.rb +49 -0
  35. data/app/models/overview_dashboard.rb +25 -0
  36. data/app/models/part.rb +28 -0
  37. data/app/models/parted.rb +32 -0
  38. data/app/models/place_edition.rb +20 -0
  39. data/app/models/programme_edition.rb +26 -0
  40. data/app/models/simple_smart_answer_edition.rb +66 -0
  41. data/app/models/simple_smart_answer_edition/node.rb +40 -0
  42. data/app/models/simple_smart_answer_edition/node/option.rb +31 -0
  43. data/app/models/tag.rb +88 -0
  44. data/app/models/transaction_edition.rb +28 -0
  45. data/app/models/travel_advice_edition.rb +177 -0
  46. data/app/models/user.rb +54 -0
  47. data/app/models/video_edition.rb +24 -0
  48. data/app/models/workflow.rb +217 -0
  49. data/app/models/workflow_actor.rb +141 -0
  50. data/app/traits/attachable.rb +60 -0
  51. data/app/traits/govspeak_smart_quotes_fixer.rb +19 -0
  52. data/app/traits/taggable.rb +113 -0
  53. data/app/validators/safe_html.rb +33 -0
  54. data/app/validators/slug_validator.rb +53 -0
  55. data/config/mongoid.yml +5 -0
  56. data/govuk_content_models.gemspec +42 -0
  57. data/jenkins.sh +7 -0
  58. data/lib/fact_check_address.rb +36 -0
  59. data/lib/govuk_content_models.rb +12 -0
  60. data/lib/govuk_content_models/require_all.rb +14 -0
  61. data/lib/govuk_content_models/test_helpers/factories.rb +213 -0
  62. data/lib/govuk_content_models/test_helpers/local_services.rb +24 -0
  63. data/lib/govuk_content_models/version.rb +4 -0
  64. data/test/fixtures/contactotron_api_response.json +1 -0
  65. data/test/fixtures/uploads/image.jpg +0 -0
  66. data/test/models/artefact_action_test.rb +123 -0
  67. data/test/models/artefact_external_link_test.rb +32 -0
  68. data/test/models/artefact_tag_test.rb +52 -0
  69. data/test/models/artefact_test.rb +583 -0
  70. data/test/models/business_support/business_size_test.rb +25 -0
  71. data/test/models/business_support/business_type_test.rb +25 -0
  72. data/test/models/business_support/location_test.rb +25 -0
  73. data/test/models/business_support/purpose_test.rb +29 -0
  74. data/test/models/business_support/sector_test.rb +25 -0
  75. data/test/models/business_support/stage_test.rb +25 -0
  76. data/test/models/business_support/support_type_test.rb +25 -0
  77. data/test/models/business_support_edition_test.rb +186 -0
  78. data/test/models/campaign_edition_test.rb +90 -0
  79. data/test/models/curated_list_test.rb +32 -0
  80. data/test/models/edition_test.rb +826 -0
  81. data/test/models/fact_check_address_test.rb +36 -0
  82. data/test/models/help_page_edition_test.rb +38 -0
  83. data/test/models/licence_edition_test.rb +104 -0
  84. data/test/models/local_authority_test.rb +113 -0
  85. data/test/models/local_service_test.rb +199 -0
  86. data/test/models/local_transaction_edition_test.rb +78 -0
  87. data/test/models/overview_dashboard_test.rb +47 -0
  88. data/test/models/simple_smart_answer_edition_test.rb +169 -0
  89. data/test/models/simple_smart_answer_node_test.rb +134 -0
  90. data/test/models/simple_smart_answer_option_test.rb +90 -0
  91. data/test/models/tag_test.rb +92 -0
  92. data/test/models/time_zone_test.rb +48 -0
  93. data/test/models/transaction_edition_test.rb +20 -0
  94. data/test/models/travel_advice_edition_test.rb +480 -0
  95. data/test/models/user_test.rb +114 -0
  96. data/test/models/video_edition_test.rb +64 -0
  97. data/test/models/workflow_actor_test.rb +61 -0
  98. data/test/models/workflow_test.rb +307 -0
  99. data/test/test_helper.rb +47 -0
  100. data/test/traits/attachable_test.rb +143 -0
  101. data/test/traits/taggable_test.rb +114 -0
  102. data/test/validators/safe_html_validator_test.rb +86 -0
  103. data/test/validators/slug_validator_test.rb +42 -0
  104. metadata +511 -0
@@ -0,0 +1,33 @@
1
+ require "govspeak"
2
+
3
+ class SafeHtml < ActiveModel::Validator
4
+ def validate(record)
5
+ record.changes.each do |field_name, (old_value, new_value)|
6
+ check_struct(record, field_name, new_value)
7
+ end
8
+ end
9
+
10
+ def check_struct(record, field_name, value)
11
+ if value.respond_to?(:values) # e.g. Hash
12
+ value.values.each { |entry| check_struct(record, field_name, entry) }
13
+ elsif value.respond_to?(:each) # e.g. Array
14
+ value.each { |entry| check_struct(record, field_name, entry) }
15
+ elsif value.is_a?(String)
16
+ check_string(record, field_name, value)
17
+ end
18
+ end
19
+
20
+ def check_string(record, field_name, string)
21
+ if record.class::GOVSPEAK_FIELDS.include?(field_name)
22
+ unless Govspeak::Document.new(string).valid?
23
+ error = "cannot include invalid Govspeak or JavaScript"
24
+ record.errors.add(field_name, error)
25
+ end
26
+ else
27
+ unless Govspeak::HtmlValidator.new(string).valid?
28
+ error = "cannot include invalid HTML or JavaScript"
29
+ record.errors.add(field_name, error)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ class SlugValidator < ActiveModel::EachValidator
2
+ # implement the method called during validation
3
+ def validate_each(record, attribute, value)
4
+ if value.to_s =~ /^done\/(.+)/
5
+ parts = [$1]
6
+ elsif value.to_s =~ /\Aforeign-travel-advice\/(.+)/ and record.kind == 'travel-advice'
7
+ parts = [$1]
8
+ elsif record.respond_to?(:kind) and record.kind == 'help_page'
9
+ if value.to_s =~ /\Ahelp\/(.+)\z/
10
+ parts = [$1]
11
+ else
12
+ record.errors[attribute] << "Help page slugs must have a help/ prefix"
13
+ return
14
+ end
15
+ elsif value.to_s =~ /\Agovernment\/(.+)/ and prefixed_inside_government_format_names.include?(record.kind)
16
+ parts = $1.split('/')
17
+ else
18
+ parts = [value]
19
+ end
20
+
21
+ if record.respond_to?(:kind)
22
+ # Inside Government formats use friendly_id to disambiguate clashes, which
23
+ # potentially results in a trailing '--1' on the last path segment.
24
+ # Rather than overriding the fairly robust parameterize-based validation
25
+ # below, we can just fudge the friendly_id added bit
26
+ if inside_government_format_names.include?(record.kind) && parts.last.include?('--')
27
+ parts.last.sub!('--', '-')
28
+ end
29
+
30
+ if prefixed_inside_government_format_names.include?(record.kind)
31
+ unless value.to_s =~ /\Agovernment\/(.+)/
32
+ record.errors[attribute] << "Inside Government slugs must have a government/ prefix"
33
+ end
34
+ end
35
+ end
36
+
37
+ parts.each do |part|
38
+ unless ActiveSupport::Inflector.parameterize(part.to_s) == part.to_s
39
+ record.errors[attribute] << "must be usable in a URL"
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+ def inside_government_format_names
46
+ Artefact::FORMATS_BY_DEFAULT_OWNING_APP["whitehall"]
47
+ end
48
+
49
+ def prefixed_inside_government_format_names
50
+ inside_government_format_names - ["detailed_guide"]
51
+ end
52
+
53
+ end
@@ -0,0 +1,5 @@
1
+ test:
2
+ host: localhost
3
+ database: govuk_content_shared_test
4
+ logger: false
5
+ use_activesupport_time_zone: true
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/govuk_content_models/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Paul Battley"]
6
+ gem.email = ["pbattley@gmail.com"]
7
+ gem.description = %q{Shared models for Panopticon and Publisher}
8
+ gem.summary = %q{Shared models for Panopticon and Publisher, as a Rails Engine}
9
+ gem.homepage = "https://github.com/alphagov/govuk_content_models"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "govuk_content_models"
15
+ gem.require_paths = ["lib", "app"]
16
+ gem.version = GovukContentModels::VERSION
17
+
18
+ gem.add_dependency "bson_ext"
19
+ gem.add_dependency "differ"
20
+ gem.add_dependency "gds-api-adapters"
21
+
22
+ gem.add_dependency "gds-sso", ">= 3.0.0", "< 4.0.0"
23
+ gem.add_dependency "govspeak", ">= 1.0.1", "< 2.0.0"
24
+ # Mongoid 2.5.0 supports the newer 1.7.x and 1.8.x Mongo drivers
25
+ gem.add_dependency "mongoid", "~> 2.5"
26
+ gem.add_dependency "plek"
27
+ gem.add_dependency "state_machine"
28
+
29
+ gem.add_development_dependency "database_cleaner", "0.7.2"
30
+ gem.add_development_dependency "factory_girl", "3.3.0"
31
+ gem.add_development_dependency "gem_publisher", "1.2.0"
32
+ gem.add_development_dependency "mocha", "0.13.3"
33
+ gem.add_development_dependency "multi_json", "1.3.7" # Pinned to allow dependency resolution
34
+ gem.add_development_dependency "rake", "0.9.2.2"
35
+ gem.add_development_dependency "webmock", "1.8.7"
36
+ gem.add_development_dependency "shoulda-context", "1.0.0"
37
+ gem.add_development_dependency "timecop", "0.5.9.2"
38
+
39
+ # The following are added to help bundler resolve dependencies
40
+ gem.add_development_dependency "rack", "~> 1.4.4"
41
+ gem.add_development_dependency "rails", "= 3.2.13"
42
+ end
data/jenkins.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -e
3
+ rm -f Gemfile.lock
4
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
5
+ export GOVUK_APP_DOMAIN=dev.gov.uk
6
+ bundle exec rake
7
+ bundle exec rake publish_gem
@@ -0,0 +1,36 @@
1
+ require "plek"
2
+
3
+ class FactCheckAddress
4
+ DOMAIN = "alphagov.co.uk"
5
+
6
+ def for_edition(edition)
7
+ "#{prefix}#{edition.id}@#{DOMAIN}"
8
+ end
9
+
10
+ def valid_address?(address)
11
+ regexp.match(address)
12
+ end
13
+
14
+ def edition_id_from_address(address)
15
+ match = valid_address?(address)
16
+ match && match[1]
17
+ end
18
+
19
+ private
20
+ def regexp
21
+ /#{Regexp.escape(prefix)}(.+?)@#{DOMAIN}/
22
+ end
23
+
24
+ def prefix
25
+ "factcheck+#{environment}-"
26
+ end
27
+
28
+ # Fact check email addresses are environment dependent. This is
29
+ # a bad thing, but changing it would be very disruptive. Since
30
+ # Plek no longer gives us access to the environment we have to rely
31
+ # on the fact that the environment is included in the public domain
32
+ # name for an app.
33
+ def environment
34
+ Plek.current.find('publisher').split('.')[1]
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ require "govuk_content_models/version"
2
+ require "mongoid"
3
+
4
+ begin
5
+ module GovukContentModels
6
+ class Engine < Rails::Engine
7
+ end
8
+ end
9
+ rescue NameError
10
+ module GovukContentModels
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # Require this file in a non-Rails app to load all the things
2
+ require "active_model"
3
+ require "mongoid"
4
+
5
+ %w[ app/models app/validators app/repositories app/traits lib ].each do |path|
6
+ full_path = File.expand_path(
7
+ "#{File.dirname(__FILE__)}/../../#{path}", __FILE__)
8
+ $LOAD_PATH.unshift full_path unless $LOAD_PATH.include?(full_path)
9
+ end
10
+
11
+ # Require everything under app
12
+ Dir.glob("#{File.dirname(__FILE__)}/../../app/**/*.rb").each do |file|
13
+ require file
14
+ end
@@ -0,0 +1,213 @@
1
+ require "factory_girl"
2
+ require "answer_edition"
3
+ require "artefact"
4
+ require "tag"
5
+ require "user"
6
+
7
+ FactoryGirl.define do
8
+ factory :user do
9
+ sequence(:uid) { |n| "uid-#{n}"}
10
+ sequence(:name) { |n| "Joe Bloggs #{n}" }
11
+ sequence(:email) { |n| "joe#{n}@bloggs.com" }
12
+ if defined?(GDS::SSO::Config)
13
+ # Grant permission to signin to the app using the gem
14
+ permissions { ["signin"] }
15
+ end
16
+ end
17
+
18
+ factory :tag do
19
+ sequence(:tag_id) { |n| "crime-and-justice/the-police-#{n}" }
20
+ sequence(:title) { |n| "The title #{n}" }
21
+ tag_type "section"
22
+ end
23
+
24
+ factory :artefact do
25
+ sequence(:name) { |n| "Artefact #{n}" }
26
+ sequence(:slug) { |n| "slug-#{n}" }
27
+ kind Artefact::FORMATS.first
28
+ owning_app 'publisher'
29
+ end
30
+
31
+ factory :non_publisher_artefact, parent: :artefact do
32
+ kind 'smart-answer'
33
+ owning_app 'smart-answers'
34
+ end
35
+
36
+ factory :edition, class: AnswerEdition do
37
+ panopticon_id {
38
+ a = create(:artefact)
39
+ a.id
40
+ }
41
+
42
+ sequence(:slug) { |n| "slug-#{n}" }
43
+ sequence(:title) { |n| "A key answer to your question #{n}" }
44
+
45
+ section "test:subsection test"
46
+
47
+ association :assigned_to, factory: :user
48
+ end
49
+ factory :answer_edition, parent: :edition do
50
+ end
51
+
52
+ factory :help_page_edition, :parent => :edition, :class => 'HelpPageEdition' do
53
+ end
54
+
55
+ factory :campaign_edition, :parent => :edition, :class => 'CampaignEdition' do
56
+ end
57
+
58
+ factory :completed_transaction_edition, :parent => :edition, :class => 'CompletedTransactionEdition' do
59
+ end
60
+
61
+ factory :video_edition, parent: :edition, :class => 'VideoEdition' do
62
+ end
63
+
64
+ factory :business_support_edition, :parent => :edition, :class => "BusinessSupportEdition" do
65
+ sequence(:business_support_identifier) {|n| "identifier-#{n}" }
66
+ end
67
+
68
+ factory :guide_edition do |ge|
69
+ panopticon_id {
70
+ a = create(:artefact)
71
+ a.id
72
+ }
73
+ ge.sequence(:title) { |n| "Test guide #{n}" }
74
+ ge.sequence(:slug) { |ns| "slug-#{ns}"}
75
+ section { "test:subsection test" }
76
+ end
77
+
78
+ factory :programme_edition do |edition|
79
+ panopticon_id {
80
+ a = create(:artefact)
81
+ a.id
82
+ }
83
+ edition.sequence(:title) { |n| "Test programme #{n}" }
84
+ edition.sequence(:slug) { |ns| "slug-#{ns}"}
85
+ section { "test:subsection test" }
86
+ end
87
+
88
+ factory :guide_edition_with_two_parts, parent: :guide_edition do
89
+ title "a title"
90
+ after :create do |getp|
91
+ getp.parts.build(title: "PART !", body: "This is some version text.",
92
+ slug: "part-one")
93
+ getp.parts.build(title: "PART !!",
94
+ body: "This is some more version text.",
95
+ slug: "part-two")
96
+ end
97
+ end
98
+
99
+ factory :guide_edition_with_two_govspeak_parts, parent: :guide_edition do
100
+ title "A title for govspeak parts"
101
+ after :create do |getp|
102
+ getp.parts.build(title: "Some Part Title!",
103
+ body: "This is some **version** text.", slug: "part-one")
104
+ getp.parts.build(title: "Another Part Title",
105
+ body: "This is [link](http://example.net/) text.",
106
+ slug: "part-two")
107
+ end
108
+ end
109
+
110
+ factory :local_transaction_edition do |lte|
111
+ panopticon_id {
112
+ a = create(:artefact)
113
+ a.id
114
+ }
115
+ title { "Test title" }
116
+ version_number 1
117
+ lte.sequence(:slug) { |ns| "slug-#{ns}"}
118
+ lte.sequence(:lgsl_code) { |nlgsl| nlgsl }
119
+ introduction { "Test introduction" }
120
+ more_information { "This is more information" }
121
+ end
122
+
123
+ factory :transaction_edition do |te|
124
+ panopticon_id {
125
+ a = create(:artefact)
126
+ a.id
127
+ }
128
+ title { "Test title" }
129
+ version_number 1
130
+ introduction { "Test introduction" }
131
+ more_information { "This is more information" }
132
+ link "http://continue.com"
133
+ will_continue_on "To be continued..."
134
+ alternate_methods "Method A or Method B"
135
+ end
136
+
137
+ factory :licence_edition, :parent => :edition, :class => "LicenceEdition" do
138
+ licence_identifier "AB1234"
139
+ end
140
+
141
+ factory :local_service do |ls|
142
+ ls.sequence(:lgsl_code)
143
+ providing_tier { %w{district unitary county} }
144
+ end
145
+
146
+ factory :local_authority do
147
+ name "Some Council"
148
+ sequence(:snac) {|n| "%02dAA" % n }
149
+ sequence(:local_directgov_id)
150
+ tier "county"
151
+ end
152
+
153
+ factory :local_authority_with_contact, parent: :local_authority do
154
+ contact_address ["line one", "line two", "line three"]
155
+ contact_url "http://www.magic.com/contact"
156
+ contact_phone "0206778654"
157
+ contact_email "contact@local.authority.gov.uk"
158
+ end
159
+
160
+ factory :local_interaction do
161
+ association :local_authority
162
+ url "http://some.council.gov/do.html"
163
+ sequence(:lgsl_code) {|n| 120 + n }
164
+ lgil_code 0
165
+ end
166
+
167
+ factory :expectation do
168
+ sequence(:text) {|n| "You will need #{n} of these."}
169
+ end
170
+
171
+ factory :place_edition do
172
+ title "Far far away"
173
+ introduction "Test introduction"
174
+ more_information "More information"
175
+ place_type "Location location location"
176
+ end
177
+
178
+ factory :curated_list do
179
+ sequence(:slug) { |n| "slug-#{n}" }
180
+ end
181
+
182
+ factory :travel_advice_edition do
183
+ sequence(:country_slug) {|n| "test-country-#{n}" }
184
+ sequence(:title) {|n| "Test Country #{n}" }
185
+ change_description "Stuff changed"
186
+ end
187
+
188
+ # These factories only work when used with FactoryGirl.create
189
+ factory :draft_travel_advice_edition, :parent => :travel_advice_edition do
190
+ end
191
+ factory :published_travel_advice_edition, :parent => :travel_advice_edition do
192
+ after :create do |tae|
193
+ tae.published_at ||= Time.zone.now.utc
194
+ tae.state = 'published'
195
+ tae.save!
196
+ end
197
+ end
198
+ factory :archived_travel_advice_edition, :parent => :travel_advice_edition do
199
+ after :create do |tae|
200
+ tae.state = 'archived'
201
+ tae.save!
202
+ end
203
+ end
204
+
205
+ factory :simple_smart_answer_edition do
206
+ panopticon_id {
207
+ a = create(:artefact)
208
+ a.id
209
+ }
210
+ title "Simple smart answer"
211
+ body "Introduction to the smart answer"
212
+ end
213
+ end
@@ -0,0 +1,24 @@
1
+ module LocalServicesHelper
2
+ def make_authority(tier, options)
3
+ authority = FactoryGirl.create(:local_authority_with_contact,
4
+ snac: options[:snac], tier: tier)
5
+ add_service_interaction(authority, options[:lgsl]) if options[:lgsl]
6
+ authority
7
+ end
8
+
9
+ def add_service_interaction(existing_authority, lgsl_code)
10
+ FactoryGirl.create(:local_interaction, local_authority: existing_authority,
11
+ lgsl_code: lgsl_code)
12
+ end
13
+
14
+ def make_service(lgsl_code, providing_tier)
15
+ LocalService.create!(lgsl_code: lgsl_code, providing_tier: providing_tier)
16
+ end
17
+
18
+ def make_authority_providing(lgsl_code)
19
+ council = FactoryGirl.create(:local_authority, snac: "00AA", tier: "county")
20
+ FactoryGirl.create(:local_interaction, local_authority: council,
21
+ lgsl_code: lgsl_code)
22
+ council
23
+ end
24
+ end