fix all broken specs + fix a small bug when comparing _auth_reset_sent_at to the current time
did
committed Nov 15, 2016
commit 92d09d396cacd2e5f20fb58b23e0b397f2f968dd
Showing 50
changed files with
355 additions
and 92 deletions
CHANGELOG.md
+0
-29
| @@ | @@ -1,29 +0,0 @@ |
| - | ### VERSION 0.1.2 |
| - | |
| - | * enhancements |
| - | * Change Markdown library from redcarpet to kramdown for support JRuby |
| - | |
| - | ### VERSION 0.1.1 |
| - | |
| - | * refactoring |
| - | * remove logger from core |
| - | |
| - | ### VERSION 0.0.1 |
| - | |
| - | * bug fix |
| - | * |
| - | |
| - | * refactoring |
| - | * |
| - | |
| - | * enhancements |
| - | * |
| - | |
| - | * backwards incompatible changes |
| - | * |
| - | |
| - | * deprecations |
| - | * |
| - | |
| - | * todos |
| - | * |
| \ No newline at end of file | |
Gemfile.lock
+0
-3
| @@ | @@ -26,7 +26,6 @@ PATH |
| sanitize (~> 4.0.1) | |
| sass (~> 3.4.21) | |
| sprockets (~> 3.5.2) | |
| - | warden (~> 1.2.6) |
| GEM | |
| remote: https://rubygems.org/ | |
| @@ | @@ -189,8 +188,6 @@ GEM |
| tins (1.12.0) | |
| tzinfo (1.2.2) | |
| thread_safe (~> 0.1) | |
| - | warden (1.2.6) |
| - | rack (>= 1.0) |
| yui-compressor (0.12.0) | |
| PLATFORMS | |
locomotive/steam/adapters/memory/condition.rb b/lib/locomotive/steam/adapters/memory/condition.rb
+1
-1
| @@ | @@ -47,7 +47,7 @@ module Locomotive::Steam |
| protected | |
| def entry_value(entry) | |
| - | value = entry.send(@field) |
| + | value = entry.send(@field) rescue nil # not just it's safe to rely on rescue (hidden errors) |
| if value.respond_to?(:translations) | |
| value[@locale] | |
locomotive/steam/entities/content_type_field.rb b/lib/locomotive/steam/entities/content_type_field.rb
+1
-0
| @@ | @@ -66,6 +66,7 @@ module Locomotive::Steam |
| when :belongs_to, :select then "#{name}_id" | |
| when :many_to_many then "#{name.singularize}_ids" | |
| when :has_many then nil | |
| + | when :password then nil |
| else name | |
| end | |
| end | |
locomotive/steam/middlewares/auth.rb b/lib/locomotive/steam/middlewares/auth.rb
+17
-0
| @@ | @@ -55,6 +55,19 @@ module Locomotive::Steam |
| append_message(status) | |
| end | |
| + | def reset_password(options) |
| + | return if authenticated? |
| + | |
| + | status, entry = services.auth.reset_password(options) |
| + | |
| + | if status == :password_reset |
| + | store_authenticated(entry) |
| + | redirect_to options.callback || mounted_on |
| + | end |
| + | |
| + | append_message(status) |
| + | end |
| + | |
| def load_authenticated_entry | |
| entry_type = request.session[:authenticated_entry_type] | |
| entry_id = request.session[:authenticated_entry_id] | |
| @@ | @@ -129,6 +142,10 @@ module Locomotive::Steam |
| params[:auth_reset_password_url] | |
| end | |
| + | def reset_token |
| + | params[:auth_reset_token] |
| + | end |
| + | |
| def from | |
| params[:auth_email_from] || 'support@locomotivecms.com' | |
| end | |
locomotive/steam/services.rb b/lib/locomotive/steam/services.rb
+1
-1
| @@ | @@ -79,7 +79,7 @@ module Locomotive |
| end | |
| register :url_builder do | |
| - | Steam::UrlBuilderService.new(current_site, locale, request, page_finder) |
| + | Steam::UrlBuilderService.new(current_site, locale, request) |
| end | |
| register :theme_asset_url do | |
locomotive/steam/services/auth_service.rb b/lib/locomotive/steam/services/auth_service.rb
+29
-2
| @@ | @@ -3,6 +3,9 @@ module Locomotive |
| class AuthService | |
| + | MIN_PASSWORD_LENGTH = 6 |
| + | RESET_TOKEN_LIFETIME = 6 * 3600 # 6 hours in seconds |
| + | |
| attr_accessor_initialize :entries, :email_service | |
| def find_authenticated_resource(type, id) | |
| @@ | @@ -32,10 +35,10 @@ module Locomotive |
| else | |
| entries.update_decorated_entry(entry, { | |
| '_auth_reset_token' => SecureRandom.hex, | |
| - | '_auth_reset_sent_at' => Time.zone.now |
| + | '_auth_reset_sent_at' => Time.zone.now.iso8601 |
| }) | |
| - | context['reset_password_url'] = options.reset_password_url + '?token=' + entry['_auth_reset_token'] |
| + | context['reset_password_url'] = options.reset_password_url + '?auth_reset_token=' + entry['_auth_reset_token'] |
| context[options.type.singularize] = entry | |
| send_reset_password_instructions(options, context) | |
| @@ | @@ -44,6 +47,30 @@ module Locomotive |
| end | |
| end | |
| + | def reset_password(options) |
| + | return :invalid_token if options.reset_token.blank? |
| + | return :password_too_short if options.password.to_s.size < MIN_PASSWORD_LENGTH |
| + | |
| + | entry = entries.all(options.type, '_auth_reset_token' => options.reset_token).first |
| + | |
| + | if entry |
| + | sent_at = Time.parse(entry[:_auth_reset_sent_at]).to_i |
| + | now = Time.zone.now.to_i - RESET_TOKEN_LIFETIME |
| + | |
| + | if sent_at >= now |
| + | entries.update_decorated_entry(entry, { |
| + | "#{options.password_field}_hash" => BCrypt::Password.create(options.password), |
| + | '_auth_reset_token' => nil, |
| + | '_auth_reset_sent_at' => nil |
| + | }) |
| + | |
| + | return [:password_reset, entry] |
| + | end |
| + | end |
| + | |
| + | :invalid_token |
| + | end |
| + | |
| private | |
| def send_reset_password_instructions(options, context) | |
locomotive/steam/services/content_entry_service.rb b/lib/locomotive/steam/services/content_entry_service.rb
+0
-2
| @@ | @@ -60,8 +60,6 @@ module Locomotive |
| with_repository(decorated_entry.content_type) do |_repository| | |
| entry = decorated_entry.__getobj__ | |
| - | puts clean_attributes(attributes).inspect |
| - | |
| entry.change(clean_attributes(attributes)) | |
| _repository.update(entry) | |
locomotive/steam/services/url_builder_service.rb b/lib/locomotive/steam/services/url_builder_service.rb
+1
-7
| @@ | @@ -3,15 +3,9 @@ module Locomotive |
| class UrlBuilderService | |
| - | attr_accessor_initialize :site, :current_locale, :request, :page_finder |
| - | |
| - | def absolute_url_for(page, locale = nil) |
| - | request.base_url + url_for(page, locale) |
| - | end |
| + | attr_accessor_initialize :site, :current_locale, :request |
| def url_for(page, locale = nil) | |
| - | page = page_finder.by_handle(page) if page.is_a?(String) |
| - | |
| prefix(_url_for(page, locale)) | |
| end | |
locomotivecms_steam.gemspec
+0
-1
| @@ | @@ -33,7 +33,6 @@ Gem::Specification.new do |spec| |
| spec.add_dependency 'dragonfly', '~> 1.0.12' | |
| spec.add_dependency 'moneta', '~> 0.8.0' | |
| spec.add_dependency 'rack_csrf', '~> 2.5.0' | |
| - | spec.add_dependency 'warden', '~> 1.2.6' |
| spec.add_dependency 'sprockets', '~> 3.5.2' | |
| spec.add_dependency 'sass', '~> 3.4.21' | |
spec/fixtures/default/app/content_types/songs.yml
+2
-1
| @@ | @@ -15,6 +15,7 @@ fields: |
| label: Cover | |
| type: file | |
| required: true | |
| + | # localized: true # required when pushing the site with Wagon |
| - short_description: | |
| type: text | |
| text_formatting: html | |
| @@ | @@ -22,4 +23,4 @@ fields: |
| type: string | |
| hint: Url to a service like Blip for instance | |
| - duration: | |
| - | hint: "format like: mm:ss" |
| \ No newline at end of file | |
| + | hint: "format like: mm:ss" |
spec/fixtures/default/app/views/pages/account/reset_password.liquid
+42
-1
| @@ | @@ -1 +1,42 @@ |
| - | reset_password.liquid |
| + | --- |
| + | title: Change your password |
| + | published: true |
| + | listed: false |
| + | handle: reset_password |
| + | --- |
| + | {% extends 'index' %} |
| + | |
| + | {% block content %} |
| + | |
| + | <h2>Change your password</h2> |
| + | |
| + | {% if current_account %} |
| + | You're already authenticated! |
| + | {% else %} |
| + | <form action="{% path_to 'reset_password' %}" method="POST"> |
| + | <input type="hidden" name="auth_action" value="reset_password" /> |
| + | <input type="hidden" name="auth_content_type" value="accounts" /> |
| + | <input type="hidden" name="auth_password_field" value="email" /> |
| + | <input type="hidden" name="auth_reset_token" value="{{ params.token }}" /> |
| + | <input type="hidden" name="auth_callback" value="{% path_to me %}" /> |
| + | |
| + | {% if auth_invalid_token %} |
| + | {{ auth_invalid_token | translate }} |
| + | {% endif %} |
| + | |
| + | {% if auth_password_too_short %} |
| + | {{ auth_password_too_short | translate }} |
| + | {% endif %} |
| + | |
| + | <div class="form-group"> |
| + | <label for="auth-password">Your new password</label> |
| + | <input type="password" class="form-control" id="auth-password" placeholder="Password" name="auth_password"> |
| + | </div> |
| + | |
| + | <button type="submit" class="btn btn-default">Submit</button> |
| + | </form> |
| + | |
| + | {% endif %} |
| + | </div> |
| + | |
| + | {% endblock %} |
spec/fixtures/default/config/translations.yml
+8
-0
| @@ | @@ -17,3 +17,11 @@ auth_wrong_email: |
| auth_reset_password_instructions_sent: | |
| en: "The instructions for changing your password have been emailed to you" | |
| fr: "Les instructions pour modifier votre mot de passe viennent de vous être envoyées" | |
| + | |
| + | auth_invalid_token: |
| + | en: "The reset token is not valid anymore" |
| + | fr: "Le token de re-initialisation du mot de passe n'est plus valide" |
| + | |
| + | auth_password_too_short: |
| + | en: "Your password is too short" |
| + | fr: "Votre mot de passe est trop court" |
spec/fixtures/default/data/accounts.yml
+12
-0
| @@ | @@ -1,3 +1,15 @@ |
| - "John": | |
| email: 'john@doe.net' | |
| password: 'easyone' | |
| + | |
| + | - "Jane": |
| + | email: 'john@doe.net' |
| + | password: 'anotherone' |
| + | _auth_reset_token: '420000000000000' |
| + | _auth_reset_sent_at: '2020-01-01T15:00:00.000Z' |
| + | |
| + | - "Mickey": |
| + | email: 'mickey@doe.net' |
| + | password: 'anotherone' |
| + | _auth_reset_token: '420000000000001' |
| + | _auth_reset_sent_at: '2016-01-01T15:00:00.000Z' |
locomotive_accounts.bson b/spec/fixtures/mongodb/locomotive_accounts.bson
+0
-0
locomotive_accounts.metadata.json b/spec/fixtures/mongodb/locomotive_accounts.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_accounts" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_accounts"}]} |
| \ No newline at end of file | |
locomotive_activities.bson b/spec/fixtures/mongodb/locomotive_activities.bson
+0
-0
locomotive_activities.metadata.json b/spec/fixtures/mongodb/locomotive_activities.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_activities" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_activities"}]} |
| \ No newline at end of file | |
locomotive_content_assets.bson b/spec/fixtures/mongodb/locomotive_content_assets.bson
+0
-0
locomotive_content_assets.metadata.json b/spec/fixtures/mongodb/locomotive_content_assets.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_content_assets" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_content_assets"}]} |
| \ No newline at end of file | |
locomotive_content_entries.bson b/spec/fixtures/mongodb/locomotive_content_entries.bson
+0
-0
locomotive_content_entries.metadata.json b/spec/fixtures/mongodb/locomotive_content_entries.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_content_entries" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_content_entries"}]} |
| \ No newline at end of file | |
locomotive_content_types.bson b/spec/fixtures/mongodb/locomotive_content_types.bson
+0
-0
locomotive_content_types.metadata.json b/spec/fixtures/mongodb/locomotive_content_types.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_content_types" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_content_types"}]} |
| \ No newline at end of file | |
locomotive_pages.bson b/spec/fixtures/mongodb/locomotive_pages.bson
+0
-0
locomotive_pages.metadata.json b/spec/fixtures/mongodb/locomotive_pages.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_pages" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_pages"}]} |
| \ No newline at end of file | |
locomotive_sites.bson b/spec/fixtures/mongodb/locomotive_sites.bson
+0
-0
locomotive_sites.metadata.json b/spec/fixtures/mongodb/locomotive_sites.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_sites" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_sites"}]} |
| \ No newline at end of file | |
locomotive_snippets.bson b/spec/fixtures/mongodb/locomotive_snippets.bson
+0
-0
locomotive_snippets.metadata.json b/spec/fixtures/mongodb/locomotive_snippets.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_snippets" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_snippets"}]} |
| \ No newline at end of file | |
locomotive_theme_assets.bson b/spec/fixtures/mongodb/locomotive_theme_assets.bson
+0
-0
locomotive_theme_assets.metadata.json b/spec/fixtures/mongodb/locomotive_theme_assets.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_theme_assets" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_theme_assets"}]} |
| \ No newline at end of file | |
locomotive_translations.bson b/spec/fixtures/mongodb/locomotive_translations.bson
+0
-0
locomotive_translations.metadata.json b/spec/fixtures/mongodb/locomotive_translations.metadata.json
+1
-1
| @@ | @@ -1 +1 @@ |
| - | { "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "locomotive_engine_wagon_dev.locomotive_translations" } ] } |
| \ No newline at end of file | |
| + | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"locomotive_engine_dev.locomotive_translations"}]} |
| \ No newline at end of file | |
system.indexes.bson b/spec/fixtures/mongodb/system.indexes.bson
+0
-0
spec/integration/repositories/content_entry_repository_spec.rb
+1
-1
| @@ | @@ -116,7 +116,7 @@ describe Locomotive::Steam::ContentEntryRepository do |
| let(:site_id) { mongodb_site_id } | |
| let(:adapter) { Locomotive::Steam::MongoDBAdapter.new(database: 'steam_test', hosts: ['127.0.0.1:27017']) } | |
| - | let(:entry_id) { BSON::ObjectId.from_string('5610310b87f6431588000029') } |
| + | let(:entry_id) { BSON::ObjectId.from_string('5829ffa087f6435971756881') } |
| end | |
spec/integration/repositories/content_type_repository_spec.rb
+1
-1
| @@ | @@ -13,7 +13,7 @@ describe Locomotive::Steam::ContentTypeRepository do |
| describe '#all' do | |
| subject { repository.all } | |
| - | it { expect(subject.size).to eq 5 } |
| + | it { expect(subject.size).to eq 6 } |
| end | |
| describe '#by_slug' do | |
spec/integration/repositories/page_repository_spec.rb
+3
-3
| @@ | @@ -14,7 +14,7 @@ describe Locomotive::Steam::PageRepository do |
| describe '#all' do | |
| let(:conditions) { {} } | |
| subject { repository.all(conditions) } | |
| - | it { expect(subject.size).to eq 26 } |
| + | it { expect(subject.size).to eq 33 } |
| context 'with conditions' do | |
| let(:conditions) { { fullpath: 'index', 'slug.ne' => '404' } } | |
| @@ | @@ -34,7 +34,7 @@ describe Locomotive::Steam::PageRepository do |
| describe '#only_handle_and_fullpath' do | |
| subject { repository.only_handle_and_fullpath } | |
| - | it { expect(subject.size).to eq 3 } |
| + | it { expect(subject.size).to eq 8 } |
| end | |
| describe '#by_fullpath' do | |
| @@ | @@ -78,7 +78,7 @@ describe Locomotive::Steam::PageRepository do |
| describe '#children_of' do | |
| let(:page) { repository.root } | |
| subject { repository.children_of(page) } | |
| - | it { expect(subject.size).to eq 15 } |
| + | it { expect(subject.size).to eq 17 } |
| end | |
| describe '#editable_element_for' do | |
spec/integration/repositories/theme_asset_repository_spec.rb
+1
-1
| @@ | @@ -26,7 +26,7 @@ describe Locomotive::Steam::ThemeAssetRepository do |
| describe '#checksums' do | |
| subject { repository.checksums } | |
| it { expect(subject.size).to eq 16 } | |
| - | it { expect(subject['stylesheets/application.css']).to eq '3bacf4c2b7877e230e6990d72dae7724' } |
| + | it { expect(subject['stylesheets/application.css']).to eq 'f431407c21db339b7759c2d7ded2553f' } |
| end | |
| end | |
spec/integration/repositories/translation_repository_spec.rb
+1
-1
| @@ | @@ -13,7 +13,7 @@ describe Locomotive::Steam::TranslationRepository do |
| describe '#all' do | |
| subject { repository.all } | |
| - | it { expect(subject.size).to eq 1 } |
| + | it { expect(subject.size).to eq 7 } |
| end | |
| describe '#by_key' do | |
spec/integration/server/auth_spec.rb
+62
-6
| @@ | @@ -109,7 +109,7 @@ describe 'Authentication' do |
| } } | |
| it 'renders the forgot password page with an error message' do | |
| - | forgot_password |
| + | post '/account/forgot-password', params |
| expect(last_response.status).to eq 200 | |
| expect(last_response.body).to include 'Forgot your password' | |
| expect(last_response.body).to include 'Your email is unknown' | |
| @@ | @@ -120,17 +120,73 @@ describe 'Authentication' do |
| let(:email) { 'john@doe.net' } | |
| it 'sends an email to the account' do | |
| - | forgot_password |
| + | post '/account/forgot-password', params |
| expect(last_response.status).to eq 200 | |
| expect(last_response.body).to include "The instructions for changing your password have been emailed to you" | |
| end | |
| end | |
| - | def forgot_password(follow_redirect = false) |
| - | post '/account/forgot-password', params |
| - | follow_redirect! if follow_redirect |
| - | last_response |
| + | end |
| + | |
| + | describe 'reset password action' do |
| + | |
| + | let(:token) { '' } |
| + | let(:new_password) { 'newone!' } |
| + | |
| + | let(:params) { { |
| + | auth_action: 'reset_password', |
| + | auth_content_type: 'accounts', |
| + | auth_password_field: 'password', |
| + | auth_password: new_password, |
| + | auth_reset_token: token, |
| + | auth_callback: '/account/me' |
| + | } } |
| + | |
| + | it 'renders the reset password page with an error message' do |
| + | post '/account/reset-password', params |
| + | expect(last_response.status).to eq 200 |
| + | expect(last_response.body).to include 'Change your password' |
| + | expect(last_response.body).to include 'The reset token is not valid anymore' |
| + | end |
| + | |
| + | context 'with an expired token' do |
| + | |
| + | let(:token) { '420000000000001' } |
| + | |
| + | it 'renders the reset password page with an error message' do |
| + | post '/account/reset-password', params |
| + | expect(last_response.status).to eq 200 |
| + | expect(last_response.body).to include 'Change your password' |
| + | expect(last_response.body).to include 'The reset token is not valid anymore' |
| + | end |
| + | |
| + | end |
| + | |
| + | context 'with a valid token' do |
| + | |
| + | let(:token) { '420000000000000' } |
| + | |
| + | it 'sends an email to the account' do |
| + | post '/account/reset-password', params |
| + | expect(last_response.status).to eq 301 |
| + | follow_redirect! |
| + | expect(last_response.body).to include "My name is Jane and I'm logged in!" |
| + | end |
| + | |
| + | context 'with a too short password' do |
| + | |
| + | let(:new_password) { 'short' } |
| + | |
| + | it 'renders the reset password page with an error message' do |
| + | post '/account/reset-password', params |
| + | expect(last_response.status).to eq 200 |
| + | expect(last_response.body).to include 'Change your password' |
| + | expect(last_response.body).to include 'Your password is too short' |
| + | end |
| + | |
| + | end |
| + | |
| end | |
| end | |
spec/integration/server/nav_spec.rb
+1
-1
| @@ | @@ -30,7 +30,7 @@ describe Locomotive::Steam::Server do |
| end | |
| it 'lists all the pages from the site liquid drop' do | |
| - | is_expected.to include('<!-- TEST -->About Us - Music - Store - Contact Us - Events - Basic page - A sample contest - Various uses of the with_scope tag - Grunge leaders - Tags - Unlisted pages - Archives - All the pages - Layouts - Songs<!-- TEST -->') |
| + | is_expected.to include('<!-- TEST -->About Us - Music - Store - Contact Us - Events - Basic page - A sample contest - Various uses of the with_scope tag - Grunge leaders - Tags - Unlisted pages - Archives - Account - All the pages - Emails - Layouts - Songs<!-- TEST -->') |
| end | |
| describe 'with wrapper' do | |
spec/integration/server/sitemap_spec.rb
+1
-1
| @@ | @@ -21,7 +21,7 @@ describe Locomotive::Steam::Server do |
| it 'checks if it looks valid' do | |
| expect(Nokogiri::XML(subject).errors.empty?).to eq true | |
| - | expect(subject.scan(/<url>/).size).to eq 40 |
| + | expect(subject.scan(/<url>/).size).to eq 45 |
| expect(subject).to match("<loc>http://example.org/songs/song-number-2/band</loc>") | |
| expect(subject).to match((<<-EOF | |
| <url> | |
spec/integration/services/content_entry_service_spec.rb
+1
-1
| @@ | @@ -45,7 +45,7 @@ describe Locomotive::Steam::ContentEntryService do |
| let(:site_id) { mongodb_site_id } | |
| let(:adapter) { Locomotive::Steam::MongoDBAdapter.new(database: 'steam_test', hosts: ['127.0.0.1:27017']) } | |
| - | let(:entry_id) { BSON::ObjectId.from_string('5610310b87f6431588000029') } |
| + | let(:entry_id) { BSON::ObjectId.from_string('5829ffa087f6435971756881') } |
| describe '#create' do | |
| subject { service.create('messages', { name: 'John', email: 'john@doe.net', message: 'Hello world!' }) } | |
spec/support/helpers.rb
+1
-1
| @@ | @@ -4,7 +4,7 @@ module Spec |
| module Helpers | |
| def mongodb_site_id | |
| - | BSON::ObjectId.from_string('561030e287f6431555000006') |
| + | BSON::ObjectId.from_string('5829ff6487f64359474164a1') |
| end | |
| def reset! | |
spec/unit/adapters/filesystem/yaml_loaders/content_entry_spec.rb
+17
-5
| @@ | @@ -6,7 +6,7 @@ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_l |
| describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do | |
| let(:site_path) { default_fixture_site_path } | |
| - | let(:content_type) { instance_double('Bands', _id: 42, slug: 'bands', association_fields: [], select_fields: [], file_fields: []) } |
| + | let(:content_type) { instance_double('Bands', _id: 42, slug: 'bands', association_fields: [], select_fields: [], file_fields: [], password_fields: []) } |
| let(:scope) { instance_double('Scope', locale: :en, context: { content_type: content_type }) } | |
| let(:loader) { described_class.new(site_path) } | |
| @@ | @@ -23,7 +23,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do |
| context 'a content type with a belongs_to field' do | |
| let(:field) { instance_double('Field', name: 'band', type: :belongs_to) } | |
| - | let(:content_type) { instance_double('Songs', slug: 'songs', association_fields: [field], select_fields: [], file_fields: []) } |
| + | let(:content_type) { instance_double('Songs', slug: 'songs', association_fields: [field], select_fields: [], file_fields: [], password_fields: []) } |
| it 'adds a new attribute for the foreign key' do | |
| expect(subject.first[:band_id]).to eq 'pearl-jam' | |
| @@ | @@ -36,7 +36,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do |
| context 'a content type with a select field' do | |
| let(:field) { instance_double('Field', name: 'kind', type: :select) } | |
| - | let(:content_type) { instance_double('Bands', slug: 'bands', select_fields: [field], association_fields: [], file_fields: []) } |
| + | let(:content_type) { instance_double('Bands', slug: 'bands', select_fields: [field], association_fields: [], file_fields: [], password_fields: []) } |
| it 'adds a new attribute for the foreign key' do | |
| expect(subject.first[:kind_id]).to eq 'grunge' | |
| @@ | @@ -45,12 +45,24 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do |
| end | |
| + | context 'a content type with a password field' do |
| + | |
| + | let(:field) { instance_double('Field', name: 'password', type: :password) } |
| + | let(:content_type) { instance_double('Accounts', slug: 'accounts', select_fields: [], association_fields: [], file_fields: [], password_fields: [field]) } |
| + | |
| + | it 'adds a new attribute for the hashed password' do |
| + | expect(subject.first[:password_hash]).not_to eq 'easyone' |
| + | expect(subject.first[:password]).to eq nil |
| + | end |
| + | |
| + | end |
| + | |
| context 'a content type with a localized field' do | |
| let(:options_scope) { instance_double('Scope', :locale= => true) } | |
| let(:options) { instance_double('SelectOptionsRepository', scope: options_scope) } | |
| let(:field) { instance_double('Field', name: 'category', type: :select, localized: true, select_options: options) } | |
| - | let(:content_type) { instance_double('Updates', slug: 'updates', select_fields: [field], association_fields: [], file_fields: []) } |
| + | let(:content_type) { instance_double('Updates', slug: 'updates', select_fields: [field], association_fields: [], file_fields: [], password_fields: []) } |
| it 'adds a new localized attribute for the foreign key' do | |
| option = instance_double('Option', _id: 'General') | |
| @@ | @@ -65,7 +77,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do |
| context 'a content type with a file field' do | |
| let(:field) { instance_double('Field', name: 'cover', type: :file) } | |
| - | let(:content_type) { instance_double('Songs', slug: 'songs', select_fields: [], association_fields: [], file_fields: [field]) } |
| + | let(:content_type) { instance_double('Songs', slug: 'songs', select_fields: [], association_fields: [], file_fields: [field], password_fields: []) } |
| it 'stores the size of the file' do | |
| expect(subject.first[:cover_size]).to eq('default' => 14768) | |
spec/unit/adapters/filesystem/yaml_loaders/content_type_spec.rb
+4
-4
| @@ | @@ -15,10 +15,10 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentType do |
| subject { loader.load(scope).sort { |a, b| a[:slug] <=> b[:slug] } } | |
| it 'tests various stuff' do | |
| - | expect(subject.size).to eq 5 |
| - | expect(subject.first[:slug]).to eq('bands') |
| - | expect(subject.first[:entries_custom_fields].size).to eq 5 |
| - | expect(subject.first[:entries_custom_fields].first[:position]).to eq 0 |
| + | expect(subject.size).to eq 6 |
| + | expect(subject[1][:slug]).to eq('bands') |
| + | expect(subject[1][:entries_custom_fields].size).to eq 5 |
| + | expect(subject[1][:entries_custom_fields].first[:position]).to eq 0 |
| end | |
| end | |
spec/unit/adapters/filesystem/yaml_loaders/page_spec.rb
+7
-7
| @@ | @@ -15,14 +15,14 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::Page do |
| subject { loader.load(scope).sort { |a, b| a[:_fullpath] <=> b[:_fullpath] } } | |
| it 'tests various stuff' do | |
| - | expect(subject.size).to eq 26 |
| + | expect(subject.size).to eq 33 |
| expect(subject.first[:title]).to eq(en: 'Page not found', fr: 'Page non trouvée') | |
| - | expect(subject[15][:is_layout]).to eq true |
| - | expect(subject[15][:listed]).to eq false |
| - | expect(subject[15][:published]).to eq false |
| - | expect(subject[16][:slug]).to eq(en: 'music', fr: 'notre-musique') |
| - | expect(subject[17][:_fullpath]).to eq 'songs' |
| - | expect(subject[17][:template_path]).to eq(en: false) |
| + | expect(subject[22][:is_layout]).to eq true |
| + | expect(subject[22][:listed]).to eq false |
| + | expect(subject[22][:published]).to eq false |
| + | expect(subject[23][:slug]).to eq(en: 'music', fr: 'notre-musique') |
| + | expect(subject[24][:_fullpath]).to eq 'songs' |
| + | expect(subject[24][:template_path]).to eq(en: false) |
| end | |
| end | |
spec/unit/adapters/filesystem/yaml_loaders/translation_spec.rb
+1
-1
| @@ | @@ -15,7 +15,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::Translation do |
| subject { loader.load(scope) } | |
| it 'tests various stuff' do | |
| - | expect(subject.size).to eq 1 |
| + | expect(subject.size).to eq 7 |
| expect(subject.first[:key]).to eq('powered_by') | |
| expect(subject.first[:values]).to eq({ 'en' => 'Powered by', 'fr' => 'Propulsé par' }) | |
| end | |
spec/unit/services/auth_service_spec.rb
+129
-0
| @@ | @@ -0,0 +1,129 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::AuthService do |
| + | |
| + | let(:entries) { instance_double('ContentService') } |
| + | let(:emails) { instance_double('EmailService') } |
| + | let(:service) { described_class.new(entries, emails) } |
| + | |
| + | let(:default_auth_options) { { |
| + | type: 'accounts', |
| + | id_field: 'email', |
| + | id: 'john@doe.net', |
| + | password_field: 'password', |
| + | password: 'easyone', |
| + | reset_password_url: '/reset-password', |
| + | reset_token: '42', |
| + | from: 'contact@acme.org', |
| + | subject: 'Instructions for changing your password', |
| + | email_handle: 'reset-password-email', |
| + | smtp: {} |
| + | } } |
| + | |
| + | let(:auth_options) { instance_double('AuthOptions', default_auth_options) } |
| + | |
| + | describe '#sign_in' do |
| + | |
| + | subject { service.sign_in(auth_options) } |
| + | |
| + | it 'returns :wrong_credentials if no entry matches the email' do |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([]) |
| + | is_expected.to eq :wrong_credentials |
| + | end |
| + | |
| + | it "returns :wrong_credentials if the password doesn't the entry's password" do |
| + | entry = build_account('fakeone') |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([entry]) |
| + | is_expected.to eq :wrong_credentials |
| + | end |
| + | |
| + | it "returns both :signed_in and the entry if the password matches the entry's password" do |
| + | entry = build_account('easyone') |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([entry]) |
| + | is_expected.to eq [:signed_in, entry] |
| + | end |
| + | |
| + | end |
| + | |
| + | describe '#forgot_password' do |
| + | |
| + | let(:liquid_context) { {} } |
| + | |
| + | subject { service.forgot_password(auth_options, liquid_context) } |
| + | |
| + | it 'returns :wrong_email if no entry matches the email' do |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([]) |
| + | is_expected.to eq :wrong_email |
| + | end |
| + | |
| + | it 'sends the instructions by email if an entry matches the email' do |
| + | allow(SecureRandom).to receive(:hex).and_return('42a') |
| + | entry = build_account('easyone', '42a') |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([entry]) |
| + | expect(entries).to receive(:update_decorated_entry) |
| + | expect(emails).to receive(:send_email).with({ |
| + | from: 'contact@acme.org', |
| + | to: 'john@doe.net', |
| + | subject: 'Instructions for changing your password', |
| + | page_handle: 'reset-password-email', |
| + | smtp: {} }, liquid_context) |
| + | is_expected.to eq :reset_password_instructions_sent |
| + | expect(liquid_context['reset_password_url']).to eq '/reset-password?auth_reset_token=42a' |
| + | end |
| + | |
| + | end |
| + | |
| + | describe '#reset_password' do |
| + | |
| + | let(:_auth_options) { default_auth_options } |
| + | let(:auth_options) { instance_double('AuthOptions', _auth_options) } |
| + | |
| + | subject { service.reset_password(auth_options) } |
| + | |
| + | context 'no auth token' do |
| + | |
| + | let(:_auth_options) { default_auth_options.merge({ reset_token: '' }) } |
| + | it { is_expected.to eq :invalid_token } |
| + | |
| + | end |
| + | |
| + | context 'password too short' do |
| + | |
| + | let(:_auth_options) { default_auth_options.merge({ password: '' }) } |
| + | it { is_expected.to eq :password_too_short } |
| + | |
| + | end |
| + | |
| + | context 'expired auth token' do |
| + | |
| + | it 'returns :invalid_token' do |
| + | entry = instance_double('Account', :[] => (Time.zone.now - 7.hours).iso8601) |
| + | expect(entries).to receive(:all).with('accounts', { '_auth_reset_token' => '42' }).and_return([entry]) |
| + | is_expected.to eq :invalid_token |
| + | end |
| + | |
| + | end |
| + | |
| + | context 'valid auth token and password' do |
| + | |
| + | it 'returns :password_reset and entry' do |
| + | entry = instance_double('Account', :[] => (Time.zone.now - 5.hours).iso8601) |
| + | expect(entries).to receive(:all).with('accounts', { '_auth_reset_token' => '42' }).and_return([entry]) |
| + | expect(BCrypt::Password).to receive(:create).with('easyone').and_return('hashedeasyone') |
| + | expect(entries).to receive(:update_decorated_entry).with(entry, { 'password_hash' => 'hashedeasyone', '_auth_reset_token' => nil, '_auth_reset_sent_at' => nil }) |
| + | is_expected.to eq [:password_reset, entry] |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | def build_account(password = 'easyone', reset_token = nil) |
| + | encrypted_password = BCrypt::Password.create(password) |
| + | entry = instance_double('Account', password: BCrypt::Password.new(encrypted_password)) |
| + | allow(entry).to receive(:[]).with(:password_hash).and_return(encrypted_password) |
| + | allow(entry).to receive(:[]).with('_auth_reset_token').and_return(reset_token) |
| + | entry |
| + | end |
| + | |
| + | end |