diff --git a/.gitignore b/.gitignore index 940069dd..4b6c5bed 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ capybara-*.html .idea .rspec +.rspec-local /log /tmp /db/*.sqlite3 diff --git a/Gemfile b/Gemfile index 2a4a4d68..9d37dd71 100644 --- a/Gemfile +++ b/Gemfile @@ -40,5 +40,5 @@ end group :test do gem 'simplecov', '~> 0.13.0', require: false gem "codeclimate-test-reporter", '~> 1.0.8', require: nil - gem 'fuubar', '~> 2.2' + gem 'shoulda', '~> 4.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 9a1d8730..b70f57ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,9 +129,6 @@ GEM faraday (0.13.1) multipart-post (>= 1.2, < 3) ffi (1.12.2) - fuubar (2.5.0) - rspec-core (~> 3.0) - ruby-progressbar (~> 1.4) globalid (0.4.2) activesupport (>= 4.2.0) grape (1.3.2) @@ -256,6 +253,12 @@ GEM unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.10.1) ruby2_keywords (0.0.2) + shoulda (4.0.0) + shoulda-context (~> 2.0) + shoulda-matchers (~> 4.0) + shoulda-context (2.0.0) + shoulda-matchers (4.4.1) + activesupport (>= 4.2.0) simplecov (0.13.0) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -301,7 +304,6 @@ DEPENDENCIES elasticsearch-model (~> 5.0.2) elasticsearch-persistence (= 5.0.2) faker (~> 1.7) - fuubar (~> 2.2) grape (~> 1.3.2) jbuilder (~> 2.7) listen @@ -315,6 +317,7 @@ DEPENDENCIES rails (~> 5.2.0) rspec-rails (~> 3.7) rubocop (= 0.52.1) + shoulda (~> 4.0) simplecov (~> 0.13.0) BUNDLED WITH diff --git a/app/classes/document_query.rb b/app/classes/document_query.rb index a87ccd36..13b02aee 100644 --- a/app/classes/document_query.rb +++ b/app/classes/document_query.rb @@ -105,7 +105,7 @@ def functions extension: %w(doc docx pdf ppt pptx xls xlsx) } }, - weight: -3 + weight: '.75' }, # Prefer documents that have been clicked more often diff --git a/app/templates/collections.rb b/app/templates/collections.rb index 6ea8ff6b..5956c0da 100644 --- a/app/templates/collections.rb +++ b/app/templates/collections.rb @@ -1,13 +1,14 @@ +# frozen_string_literal: true + class Collections include Templatable def body Jbuilder.encode do |json| - json.template "*-#{I14y::APP_NAME}-collections-*" + json.index_patterns "*-#{I14y::APP_NAME}-collections-*" json.mappings do json.collection do dynamic_templates(json) - json._all { json.enabled false } end end end @@ -18,4 +19,4 @@ def dynamic_templates(json) string_fields_template(json, "keyword") end end -end \ No newline at end of file +end diff --git a/app/templates/documents.rb b/app/templates/documents.rb index ce5d93b0..2a0b8af5 100644 --- a/app/templates/documents.rb +++ b/app/templates/documents.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Documents include Templatable LIGHT_STEMMERS = { fr: "french", de: "german", es: "spanish", it: "italian", pt: "portuguese" } @@ -10,7 +12,7 @@ def initialize def body Jbuilder.encode do |json| - json.template "*-#{I14y::APP_NAME}-documents-*" + json.index_patterns "*-#{I14y::APP_NAME}-documents-*" json.settings do json.analysis do char_filter(json) @@ -23,7 +25,6 @@ def body json.document do dynamic_templates(json) properties(json) - json._all { json.enabled false } end end end diff --git a/spec/lib/serde_spec.rb b/spec/lib/serde_spec.rb index 58922834..eac6c4d5 100644 --- a/spec/lib/serde_spec.rb +++ b/spec/lib/serde_spec.rb @@ -37,6 +37,31 @@ } ) end + + context 'when language fields contain HTML/CSS' do + let(:html) do + <<~HTML +
+

hello & goodbye!

+ HTML + end + + let(:original_hash) do + ActiveSupport::HashWithIndifferentAccess.new( + title: 'foo', + description: html, + content: "this is html" + ) + end + + it 'sanitizes the language fields' do + expect(serialize_hash).to match(hash_including( + title_en: 'foo', + description_en: 'hello & goodbye!', + content_en: 'this is html' + )) + end + end end describe '.deserialize_hash' do diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb new file mode 100644 index 00000000..3dfa1903 --- /dev/null +++ b/spec/models/collection_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +describe Collection do + subject(:collection) { described_class.new(collection_params) } + + let(:id) { 'agency_blogs' } + let(:token) { 'secret' } + let(:collection_params) do + { + _id: id, + token: token + } + end + + it { is_expected.to be_valid } + + describe 'attributes' do + it do + is_expected.to have_attributes( + token: 'secret', + id: 'agency_blogs' + ) + end + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:token) } + end +end diff --git a/spec/models/document_spec.rb b/spec/models/document_spec.rb index 1419e2d3..cddf88ae 100644 --- a/spec/models/document_spec.rb +++ b/spec/models/document_spec.rb @@ -7,72 +7,37 @@ language: 'en', path: 'http://www.agency.gov/page1.html', title: 'My Title', - created: DateTime.now, - changed: DateTime.now, + created: DateTime.new(2020, 1, 1), + changed: DateTime.new(2020, 1, 2), description: 'My Description', content: 'some content', promote: true, - tags: 'this,that' + tags: 'this,that', + click_count: 5 } end - before(:all) do - handle = 'test_index' - Elasticsearch::Persistence.client.indices.delete( - index: [Document.index_namespace(handle), '*'].join('-') - ) - es_documents_index_name = [Document.index_namespace(handle), 'v1'].join('-') - Document.create_index!(index: es_documents_index_name) - Elasticsearch::Persistence.client.indices.put_alias index: es_documents_index_name, - name: Document.index_namespace(handle) - Document.index_name = Document.index_namespace(handle) - end - - after(:all) do - Elasticsearch::Persistence.client.indices.delete( - index: [Document.index_namespace('test_index'), '*'].join('-') - ) - end - - describe '.create' do - context 'when language fields contain HTML/CSS and HTML entities' do - let(:html) do - <<~HTML -
-

hello & goodbye!

- HTML - end - - before do - Document.create(_id: 'a123', - language: 'en', - title: 'foo', - description: html, - created: DateTime.now, - path: 'http://www.agency.gov/page1.html', - content: "this is html") - end - - it 'sanitizes the language fields' do - document = Document.find 'a123' - expect(document.title).to eq('foo') - expect(document.description).to eq('hello & goodbye!') - expect(document.content).to eq('this is html') - end + describe 'attributes' do + subject(:document) { described_class.new(valid_params) } + + it do + is_expected.to have_attributes( + language: 'en', + path: 'http://www.agency.gov/page1.html', + title: 'My Title', + created: DateTime.new(2020, 1, 1), + changed: DateTime.new(2020, 1, 2), + description: 'My Description', + content: 'some content', + promote: true, + tags: 'this,that', + click_count: 5 + ) end + end - context 'when a created value is provided but not changed' do - let(:params_without_changed) do - valid_params.merge(created: DateTime.now, changed: '') - end - - before { Document.create(params_without_changed) } - - it 'sets "changed" to be the same as "created"' do - Document.create(params_without_changed) - document = Document.find('a123') - expect(document.changed).to eq document.created - end - end + describe 'validations' do + it { is_expected.to validate_presence_of(:path) } + it { is_expected.to validate_presence_of(:language) } end end diff --git a/spec/requests/api/v1/collections_spec.rb b/spec/requests/api/v1/collections_spec.rb index edf66f30..5b8892df 100644 --- a/spec/requests/api/v1/collections_spec.rb +++ b/spec/requests/api/v1/collections_spec.rb @@ -50,6 +50,8 @@ it 'stores the appropriate fields in the Elasticsearch collection' do collection = Collection.find('agency_blogs') expect(collection.token).to eq('secret') + expect(collection.created_at).to be_an_instance_of(Time) + expect(collection.updated_at).to be_an_instance_of(Time) end it_behaves_like 'a data modifying request made during read-only mode' diff --git a/spec/requests/api/v1/documents_spec.rb b/spec/requests/api/v1/documents_spec.rb index b667c3d9..487a501d 100644 --- a/spec/requests/api/v1/documents_spec.rb +++ b/spec/requests/api/v1/documents_spec.rb @@ -66,6 +66,25 @@ expect(document.description).to eq('my desc') expect(document.content).to eq('my content') expect(document.tags).to match_array(['bar blat', 'foo']) + expect(document.created_at).to be_an_instance_of(Time) + expect(document.updated_at).to be_an_instance_of(Time) + end + + context 'when a "created" value is provided but not "changed"' do + let(:valid_params) do + { document_id: id, + title: 'my title', + path: 'http://www.gov.gov/goo.html', + description: 'my desc', + language: 'hy', + content: 'my content', + created: '2020-01-01T10:00:00Z' } + end + + it 'sets "changed" to be the same as "created"' do + document = Document.find(id) + expect(document.changed).to eq '2020-01-01T10:00:00Z' + end end it_behaves_like 'a data modifying request made during read-only mode' @@ -260,6 +279,13 @@ end describe 'PUT /api/v1/documents/{document_id}' do + subject(:put_document) do + put "/api/v1/documents/#{URI.encode(id)}", + params: update_params, + headers: valid_session + Document.refresh_index! + end + let(:update_params) do { title: 'new title', @@ -285,7 +311,7 @@ promote: true, path: 'http://www.gov.gov/url4.html') - api_put "/api/v1/documents/#{URI.encode(id)}", update_params, valid_session + put_document end it 'returns success message as JSON' do @@ -310,6 +336,39 @@ it_behaves_like 'a data modifying request made during read-only mode' end + + context 'when time has passed since the document was created' do + before do + document_create(_id: id, + language: 'en', + title: 'hi there 4', + description: 'bigger desc 4', + content: 'huge content 4', + path: 'http://www.gov.gov/url4.html') + # Force-update the timestamps to avoid fooling the specs with any + # automagic trickery + Elasticsearch::Persistence.client.update( + index: Document.index_name, + id: id, + body: { + doc: { + updated_at: 1.year.ago, + created_at: 1.year.ago + } + }, + type: 'document' + ) + Document.refresh_index! + end + + it 'updates the updated_at timestamp' do + expect { put_document }.to change { Document.find(id).updated_at } + end + + it 'does not update the created_at timestamp' do + expect { put_document }.not_to change { Document.find(id).created_at } + end + end end describe 'DELETE /api/v1/documents/{document_id}' do diff --git a/spec/support/document_crud.rb b/spec/support/document_crud.rb index eac1e3b9..6a46bcf3 100644 --- a/spec/support/document_crud.rb +++ b/spec/support/document_crud.rb @@ -10,13 +10,8 @@ def api_post(params,session) Document.refresh_index! end - def api_put(path,params, session) - put path, params: params, headers: session - Document.refresh_index! - end - def api_delete(path,session) delete path, headers: session end -end \ No newline at end of file +end diff --git a/spec/support/shoulda.rb b/spec/support/shoulda.rb new file mode 100644 index 00000000..7d045f35 --- /dev/null +++ b/spec/support/shoulda.rb @@ -0,0 +1,6 @@ +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end