govuk_content_models 6.0.2

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.
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