diff options
51 files changed, 397 insertions, 122 deletions
| diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb new file mode 100644 index 000000000..e38a92982 --- /dev/null +++ b/app/controllers/statuses_controller.rb @@ -0,0 +1,20 @@ +class StatusesController < ChouetteController +  respond_to :json + +  def index + +    status = { +      referentials_blocked: Referential.blocked.count, +      imports_blocked: Import.blocked.count, +      compliance_check_sets_blocked: ComplianceCheckSet.blocked.count +    } +    status[:status] = global_status status +    render json: status.to_json +  end + +  private + +  def global_status status +    status.values.all?(&:zero?) ? 'ok' : 'ko' +  end +end diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb index 95b1fd959..e4f9d7dbc 100644 --- a/app/decorators/time_table_decorator.rb +++ b/app/decorators/time_table_decorator.rb @@ -14,7 +14,7 @@ class TimeTableDecorator < AF83::Decorator        l.href { [:edit, context[:referential], object] }      end -    instance_decorator.action_link if: ->{ object.calendar }, secondary: true do |l| +    instance_decorator.action_link policy: :actualize, if: ->{ object.calendar }, secondary: true do |l|        l.content t('actions.actualize')        l.href do           h.actualize_referential_time_table_path( diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 279995741..2068dd23c 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -330,7 +330,7 @@ module TableBuilderHelper      else        menu = content_tag :ul, class: 'dropdown-menu' do          ( -          CustomLinks.new(item, pundit_user, links, referential).links + +          CustomLinks.new(item, pundit_user, links, referential, workgroup).links +            action_links.select { |link| link.is_a?(Link) }          ).map do |link|            gear_menu_link(link) diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js index 158deaa17..035bce155 100644 --- a/app/javascript/routes/components/BSelect2.js +++ b/app/javascript/routes/components/BSelect2.js @@ -85,7 +85,7 @@ class BSelect2 extends Component{          onSelect={ this.props.onSelect }          ref='newSelect'          options={{ -          placeholder: this.context.I18n.routes.edit.select2.placeholder, +          placeholder: this.context.I18n.t("routes.edit.select2.placeholder"),            allowClear: true,            language: 'fr', /* Doesn't seem to work... :( */            theme: 'bootstrap', diff --git a/app/javascript/routes/components/OlMap.js b/app/javascript/routes/components/OlMap.js index 056bddbcb..4beb02872 100644 --- a/app/javascript/routes/components/OlMap.js +++ b/app/javascript/routes/components/OlMap.js @@ -115,40 +115,40 @@ export default class OlMap extends Component{                <strong>{this.props.value.olMap.json.name}</strong>              </p>              <p> -              <strong>{this.context.I18n.routes.edit.stop_point_type} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.stop_point_type')} : </strong>                {this.props.value.olMap.json.area_type}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.short_name} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.short_name')} : </strong>                {this.props.value.olMap.json.short_name}              </p>              <p> -              <strong>{this.context.I18n.id_reflex} : </strong> +              <strong>{this.context.I18n.t('id_reflex')} : </strong>                {this.props.value.olMap.json.user_objectid}              </p> -            <p><strong>{this.context.I18n.routes.edit.map.coordinates} : </strong></p> +            <p><strong>{this.context.I18n.t('routes.edit.map.coordinates')} : </strong></p>              <p style={{paddingLeft: 10, marginTop: 0}}> -              <em>{this.context.I18n.routes.edit.map.proj}.: </em>WSG84<br/> -              <em>{this.context.I18n.routes.edit.map.lat}.: </em>{this.props.value.olMap.json.latitude} <br/> -              <em>{this.context.I18n.routes.edit.map.lon}.: </em>{this.props.value.olMap.json.longitude} +              <em>{this.context.I18n.t('routes.edit.map.proj')}.: </em>WSG84<br/> +              <em>{this.context.I18n.t('routes.edit.map.lat')}.: </em>{this.props.value.olMap.json.latitude} <br/> +              <em>{this.context.I18n.t('routes.edit.map.lon')}.: </em>{this.props.value.olMap.json.longitude}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.postal_code} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.postal_code')} : </strong>                {this.props.value.olMap.json.zip_code}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.city} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.city')} : </strong>                {this.props.value.olMap.json.city_name}              </p>              <p> -              <strong>{this.context.I18n.routes.edit.map.comment} : </strong> +              <strong>{this.context.I18n.t('routes.edit.map.comment')} : </strong>                {this.props.value.olMap.json.comment}              </p>              {(this.props.value.stoparea_id != this.props.value.olMap.json.stoparea_id) &&(                <div className='btn btn-outline-primary btn-sm'                  onClick= {() => {this.props.onUpdateViaOlMap(this.props.index, this.props.value.olMap.json)}} -              >{this.context.I18n.actions.select}</div> +              >{this.context.I18n.t('actions.select')}</div>              )}            </div>              <div className='map_content'> @@ -162,7 +162,7 @@ export default class OlMap extends Component{    }  } -OlMap.PropTypes = { +OlMap.propTypes = {  }  OlMap.contextTypes = { diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index 2d47e802b..af51a6bb4 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -18,15 +18,15 @@ export default function StopPoint(props, {I18n}) {          <div>            <select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}> -            <option value="normal">{I18n.routes.edit.stop_point.boarding.normal}</option> -            <option value="forbidden">{I18n.routes.edit.stop_point.boarding.forbidden}</option> +            <option value="normal">{I18n.t('routes.edit.stop_point.boarding.normal')}</option> +            <option value="forbidden">{I18n.t('routes.edit.stop_point.boarding.forbidden')}</option>            </select>          </div>          <div>            <select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}> -            <option value="normal">{I18n.routes.edit.stop_point.alighting.normal}</option> -            <option value="forbidden">{I18n.routes.edit.stop_point.alighting.forbidden}</option> +            <option value="normal">{I18n.t('routes.edit.stop_point.alighting.normal')}</option> +            <option value="forbidden">{I18n.t('routes.edit.stop_point.alighting.forbidden')}</option>            </select>          </div> @@ -77,7 +77,7 @@ export default function StopPoint(props, {I18n}) {    )  } -StopPoint.PropTypes = { +StopPoint.propTypes = {    onToggleMap: PropTypes.func.isRequired,    onToggleEdit: PropTypes.func.isRequired,    onDeleteClick: PropTypes.func.isRequired, @@ -93,4 +93,4 @@ StopPoint.PropTypes = {  StopPoint.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/routes/components/StopPointList.js b/app/javascript/routes/components/StopPointList.js index b39fa0c9c..b227abdea 100644 --- a/app/javascript/routes/components/StopPointList.js +++ b/app/javascript/routes/components/StopPointList.js @@ -10,22 +10,22 @@ export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick          <div className="wrapper">            <div style={{width: 100}}>              <div className="form-group"> -              <label className="control-label">{I18n.reflex_id}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.reflex_id')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.name}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.name')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.for_boarding}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_boarding')}</label>              </div>            </div>            <div>              <div className="form-group"> -              <label className="control-label">{I18n.simple_form.labels.stop_point.for_alighting}</label> +              <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_alighting')}</label>              </div>            </div>            <div className='actions-5'></div> diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 4a36ec4e1..98b9eab4b 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -8,7 +8,7 @@ const I18n = clone(window, "I18n")  const actions = {    weekDays: (index) => { -    return range(1, 8).map(n => I18n.time_tables.edit.metas.days[n]) +    return range(1, 8).map(n => I18n.t('time_tables.edit.metas.days')[n])    },    strToArrayDayTypes: (str) =>{      return actions.weekDays().map(day => str.indexOf(day) !== -1) @@ -155,7 +155,7 @@ const actions = {      type : 'CLOSE_MODAL'    }),    monthName(strDate) { -    let monthList = range(1,13).map(n => I18n.calendars.months[n]) +    let monthList = range(1,13).map(n => I18n.t('calendars.months.'+ n ))      let date = new Date(strDate)      return monthList[date.getUTCMonth()]    }, @@ -225,7 +225,7 @@ const actions = {        let period = periods[i]        if (index !== i && !period.deleted) {          if (new Date(period.period_start) <= end && new Date(period.period_end) >= start)  { -          error = I18n.time_tables.edit.error_submit.periods_overlaps +          error = I18n.t('time_tables.edit.error_submit.periods_overlaps')            break          }        } @@ -239,7 +239,7 @@ const actions = {      for (let day of in_days) {        if (start <= new Date(day.date) && end >= new Date(day.date)) { -        error = I18n.time_tables.edit.error_submit.dates_overlaps +        error = I18n.t('time_tables.edit.error_submit.dates_overlaps')          break        }      } @@ -316,9 +316,9 @@ const actions = {    errorModalMessage: (errorKey) => {      switch (errorKey) {        case "withoutPeriodsWithDaysTypes": -        return I18n.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes +        return I18n.t('time_tables.edit.error_modal.withoutPeriodsWithDaysTypes')        case "withPeriodsWithoutDayTypes": -        return I18n.time_tables.edit.error_modal.withPeriodsWithoutDayTypes +        return I18n.t('time_tables.edit.error_modal.withPeriodsWithoutDayTypes')        default:          return errorKey diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js index 845e7ed1b..4e8583bc0 100644 --- a/app/javascript/time_tables/components/ConfirmModal.js +++ b/app/javascript/time_tables/components/ConfirmModal.js @@ -9,11 +9,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan          <div className='modal-dialog'>            <div className='modal-content'>              <div className='modal-header'> -              <h4 className='modal-title'>{I18n.time_tables.edit.confirm_modal.title}</h4> +              <h4 className='modal-title'>{I18n.t('time_tables.edit.confirm_modal.title')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> -                <p>{I18n.time_tables.edit.confirm_modal.message}</p> +                <p>{I18n.t('time_tables.edit.confirm_modal.message')}</p>                </div>              </div>              <div className='modal-footer'> @@ -23,7 +23,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalCancel(modal.confirmModal.callback) }}                > -                {I18n.cancel} +                {I18n.t('cancel')}                </button>                <button                  className='btn btn-primary' @@ -31,7 +31,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan                  type='button'                  onClick={() => { onModalAccept(modal.confirmModal.callback, timetable, metas) }}                > -                {I18n.actions.submit} +                {I18n.t('actions.submit')}                </button>              </div>            </div> @@ -49,4 +49,4 @@ ConfirmModal.propTypes = {  ConfirmModal.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/time_tables/components/ErrorModal.js b/app/javascript/time_tables/components/ErrorModal.js index 543177e54..8af12f1d1 100644 --- a/app/javascript/time_tables/components/ErrorModal.js +++ b/app/javascript/time_tables/components/ErrorModal.js @@ -10,7 +10,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {          <div className='modal-dialog'>            <div className='modal-content'>              <div className='modal-header'> -              <h4 className='modal-title'>{I18n.time_tables.edit.error_modal.title}</h4> +              <h4 className='modal-title'>{I18n.t('time_tables.edit.error_modal.title')}</h4>              </div>              <div className='modal-body'>                <div className='mt-md mb-md'> @@ -24,7 +24,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {                  type='button'                  onClick={() => { onModalClose() }}                > -                {I18n.back} +                {I18n.t('back')}                </button>              </div>            </div> @@ -41,4 +41,4 @@ ErrorModal.propTypes = {  ErrorModal.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js index 3c6848d27..08a6e26fe 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -13,7 +13,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* comment (name) */}            <div className="form-group">              <label htmlFor="" className="control-label col-sm-4 required"> -              {I18n.time_tables.edit.metas.name} <abbr title="">*</abbr> +              {I18n.t('time_tables.edit.metas.name')} <abbr title="">*</abbr>              </label>              <div className="col-sm-8">                <input @@ -28,7 +28,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* color */}            {metas.color !== undefined && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.color}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'color')}</label>              <div className="col-sm-8">                <div className="dropdown color_selector">                  <button @@ -73,7 +73,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* tags */}            {metas.tags !== undefined && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.tag_list}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'tag_list')}</label>              <div className="col-sm-8">                <TagsSelect2                  initialTags={metas.initial_tags} @@ -86,16 +86,16 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            {/* calendar */}            {metas.calendar !== null && <div className="form-group"> -            <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.calendar}</label> +            <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'calendar')}</label>              <div className="col-sm-8"> -              <span>{metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}</span> +              <span>{metas.calendar ? metas.calendar.name : I18n.t('time_tables.edit.metas.no_calendar')}</span>              </div>            </div>}            {/* day_types */}            <div className="form-group">              <label htmlFor="" className="control-label col-sm-4"> -              {I18n.time_tables.edit.metas.day_types} +              {I18n.t('time_tables.edit.metas.day_types')}              </label>              <div className="col-sm-8">                <div className="form-group labelled-checkbox-group"> diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js index 085654a88..d17a246f7 100644 --- a/app/javascript/time_tables/components/PeriodForm.js +++ b/app/javascript/time_tables/components/PeriodForm.js @@ -46,7 +46,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      <div>                        <div className="form-group">                          <label htmlFor="" className="control-label required"> -                          {I18n.time_tables.edit.period_form.begin} +                          {I18n.t('time_tables.edit.period_form.begin')}                            <abbr title="requis">*</abbr>                          </label>                        </div> @@ -54,7 +54,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      <div>                        <div className="form-group">                          <label htmlFor="" className="control-label required"> -                          {I18n.time_tables.edit.period_form.end} +                          {I18n.t('time_tables.edit.period_form.end')}                            <abbr title="requis">*</abbr>                          </label>                        </div> @@ -105,14 +105,14 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                      className='btn btn-link'                      onClick={onClosePeriodForm}                    > -                    {I18n.cancel} +                    {I18n.t('cancel')}                    </button>                    <button                      type='button'                      className='btn btn-outline-primary mr-sm'                      onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas, filter(timetable.time_table_dates, ['in_out', true]))}                    > -                    {I18n.actions.submit} +                    {I18n.t('actions.submit')}                    </button>                  </div>                </div> @@ -124,7 +124,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm                    className='btn btn-outline-primary'                    onClick={onOpenAddPeriodForm}                  > -                  {I18n.time_tables.actions.add_period} +                  {I18n.t('time_tables.actions.add_period')}                  </button>                </div>              } @@ -132,7 +132,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm          </div>        </div>      </div> -  )  +  )  }  PeriodForm.propTypes = { @@ -147,4 +147,4 @@ PeriodForm.propTypes = {  PeriodForm.contextTypes = {    I18n: PropTypes.object -}
\ No newline at end of file +} diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js index dc3739d58..43cf59fdf 100644 --- a/app/javascript/time_tables/components/TagsSelect2.js +++ b/app/javascript/time_tables/components/TagsSelect2.js @@ -40,7 +40,7 @@ export default class TagsSelect2 extends Component {            allowClear: true,            theme: 'bootstrap',            width: '100%', -          placeholder: this.context.I18n.time_tables.edit.select2.tag.placeholder, +          placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'),            ajax: {              url: origin + path + '/tags.json',              dataType: 'json', diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js index c44f2a134..991f31435 100644 --- a/app/javascript/time_tables/components/Timetable.js +++ b/app/javascript/time_tables/components/Timetable.js @@ -31,11 +31,11 @@ export default class Timetable extends Component {          <div className="table table-2entries mb-sm">            <div className="t2e-head w20">              <div className="th"> -              <div className="strong">{this.context.I18n.time_tables.synthesis}</div> +              <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div>              </div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.day_types}</span></div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.periods}</span></div> -            <div className="td"><span>{this.context.I18n.time_tables.edit.exceptions}</span></div> +            <div className="td"><span>{this.context.I18n.t('time_tables.edit.day_types')}</span></div> +            <div className="td"><span>{this.context.I18n.t('time_tables.edit.periods')}</span></div> +            <div className="td"><span>{this.context.I18n.t('time_tables.edit.exceptions')}</span></div>            </div>            <div className="t2e-item-list w80">              <div> diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 47c18af09..5cc5d8b0d 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -133,7 +133,7 @@ module Chouette      def checksum_attributes        values = self.slice(*['name', 'published_name', 'wayback']).values        values.tap do |attrs| -        attrs << self.stop_points.sort_by(&:position).map{|sp| "#{sp.stop_area.user_objectid}#{sp.for_boarding}#{sp.for_alighting}" }.join +        attrs << self.stop_points.sort_by(&:position).map{|sp| [sp.stop_area.user_objectid, sp.for_boarding, sp.for_alighting]}          attrs << self.routing_constraint_zones.map(&:checksum)        end      end diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb index 903922241..58703598e 100644 --- a/app/models/chouette/routing_constraint_zone.rb +++ b/app/models/chouette/routing_constraint_zone.rb @@ -25,7 +25,9 @@ module Chouette      end      def checksum_attributes -      self.stop_points.map(&:stop_area).map(&:user_objectid) +      [ +        self.stop_points.map(&:stop_area).map(&:user_objectid) +      ]      end      def stop_points_belong_to_route diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index f4c44d26d..289fc134f 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -19,6 +19,20 @@ class ComplianceCheckSet < ActiveRecord::Base      where('created_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)    end +  scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } + +  def self.finished_statuses +    %w(successful failed warning aborted canceled) +  end + +  def self.abort_old +    where( +      'created_at < ? AND status NOT IN (?)', +      4.hours.ago, +      finished_statuses +    ).update_all(status: 'aborted') +  end +    def notify_parent      if parent        # parent.child_change diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb index a76995b0f..92103798e 100644 --- a/app/models/concerns/checksum_support.rb +++ b/app/models/concerns/checksum_support.rb @@ -24,10 +24,29 @@ module ChecksumSupport      self.attributes.values    end +  def checksum_replace_nil_or_empty_values values +    # Replace empty array by nil & nil by VALUE_FOR_NIL_ATTRIBUTE +    values +      .map { |x| x.present? && x || VALUE_FOR_NIL_ATTRIBUTE } +      .map do |item| +        item = +          if item.kind_of?(Array) +            checksum_replace_nil_or_empty_values(item) +          else +            item +          end +      end +  end +    def current_checksum_source -    source = self.checksum_attributes.map{ |x| x unless x.try(:empty?) } -    source = source.map{ |x| x || VALUE_FOR_NIL_ATTRIBUTE } -    source.map(&:to_s).join(SEPARATOR) +    source = checksum_replace_nil_or_empty_values(self.checksum_attributes) +    source.map{ |item| +      if item.kind_of?(Array) +        item.map{ |x| x.kind_of?(Array) ? "(#{x.join(',')})" : x }.join(',') +      else +        item +      end +    }.join(SEPARATOR)    end    def set_current_checksum_source diff --git a/app/models/concerns/min_max_values_validation.rb b/app/models/concerns/min_max_values_validation.rb index 9b2e0d548..eff779d81 100644 --- a/app/models/concerns/min_max_values_validation.rb +++ b/app/models/concerns/min_max_values_validation.rb @@ -2,6 +2,7 @@ module MinMaxValuesValidation    extend ActiveSupport::Concern    included do +    validates_presence_of :minimum, :maximum      validate :min_max_values_validation    end diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb index 8c49723fe..5242abc33 100644 --- a/app/models/concerns/timetable_support.rb +++ b/app/models/concerns/timetable_support.rb @@ -101,7 +101,7 @@ module TimetableSupport        period.period_start = Date.parse(item['period_start'])        period.period_end   = Date.parse(item['period_end']) -      period.save if period === ActiveRecord::Base && period.changed? +      period.save if period.is_a?(ActiveRecord::Base) && period.changed?        item['id'] = period.id      end diff --git a/app/models/import.rb b/app/models/import.rb index 049a65f40..29aadcd56 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -13,6 +13,8 @@ class Import < ActiveRecord::Base      where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)     end +  scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } +    extend Enumerize    enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new @@ -42,6 +44,14 @@ class Import < ActiveRecord::Base      %w(successful failed warning aborted canceled)    end +  def self.abort_old +    where( +      'created_at < ? AND status NOT IN (?)', +      4.hours.ago, +      finished_statuses +    ).update_all(status: 'aborted') +  end +    def notify_parent      parent.child_change      update(notified_parent_at: DateTime.now) diff --git a/app/models/referential.rb b/app/models/referential.rb index f64db4ebf..509e0412f 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -62,6 +62,7 @@ class Referential < ActiveRecord::Base    scope :order_by_validity_period, ->(dir) { joins(:metadatas).order("unnest(periodes) #{dir}") }    scope :order_by_lines, ->(dir) { joins(:metadatas).group("referentials.id").order("sum(array_length(referential_metadata.line_ids,1)) #{dir}") }    scope :not_in_referential_suite, -> { where referential_suite_id: nil } +  scope :blocked, -> { where('ready = ? AND created_at < ?', false, 4.hours.ago) }    def save_with_table_lock_timeout(options = {})      save_without_table_lock_timeout(options) diff --git a/app/models/vehicle_journey_control/delta.rb b/app/models/vehicle_journey_control/delta.rb index f061b9fdd..737b7d78c 100644 --- a/app/models/vehicle_journey_control/delta.rb +++ b/app/models/vehicle_journey_control/delta.rb @@ -4,6 +4,7 @@ module VehicleJourneyControl      store_accessor :control_attributes, :maximum      validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 +    validates_presence_of :maximum      def self.default_code; "3-VehicleJourney-3" end    end diff --git a/app/models/vehicle_journey_control/waiting_time.rb b/app/models/vehicle_journey_control/waiting_time.rb index f2666cb72..89a18a5d9 100644 --- a/app/models/vehicle_journey_control/waiting_time.rb +++ b/app/models/vehicle_journey_control/waiting_time.rb @@ -3,6 +3,7 @@ module VehicleJourneyControl      store_accessor :control_attributes, :maximum      validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0 +    validates_presence_of :maximum      def self.default_code; "3-VehicleJourney-1" end    end diff --git a/app/services/parent_import_notifier.rb b/app/services/parent_import_notifier.rb deleted file mode 100644 index 47e6755e4..000000000 --- a/app/services/parent_import_notifier.rb +++ /dev/null @@ -1,15 +0,0 @@ -class ParentImportNotifier -  def self.notify_when_finished(imports = nil) -    imports ||= imports_pending_notification -    imports.each(&:notify_parent) -  end - -  def self.imports_pending_notification -    Import -      .where( -        notified_parent_at: nil, -        status: Import.finished_statuses -      ) -      .where.not(parent: nil) -  end -end diff --git a/app/services/parent_notifier.rb b/app/services/parent_notifier.rb new file mode 100644 index 000000000..653c98aff --- /dev/null +++ b/app/services/parent_notifier.rb @@ -0,0 +1,19 @@ +class ParentNotifier +  def initialize(klass) +    @klass = klass +  end + +  def notify_when_finished(collection = nil) +    collection ||= objects_pending_notification +    collection.each(&:notify_parent) +  end + +  def objects_pending_notification +    @klass +      .where( +        notified_parent_at: nil, +        status: @klass.finished_statuses +      ) +      .where.not(parent: nil) +  end +end diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index f17b5d4d4..0b58c0c72 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -16,7 +16,7 @@                  key: :name, \                  attribute: 'name', \                  link_to: lambda do |calendar| \ -                  workgroup_calendar_path(current_workgroup, calendar) \ +                  workgroup_calendar_path(workgroup, calendar) \                  end \                ), \                TableBuilderHelper::Column.new( \ diff --git a/app/views/time_tables/_form.html.slim b/app/views/time_tables/_form.html.slim index d06fdf444..007044e65 100644 --- a/app/views/time_tables/_form.html.slim +++ b/app/views/time_tables/_form.html.slim @@ -5,7 +5,7 @@        = form.input :comment, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.time_table.comment")}        - if @time_table.new_record? && !@time_table.created_from -        = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_calendars_path}} +        = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_workgroup_calendars_path(current_workgroup)}}        - if @time_table.created_from          = form.input :created_from, disabled: true, input_html: { value: @time_table.created_from.comment } diff --git a/config/deploy.rb b/config/deploy.rb index 3353b4186..833ecfbbe 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -104,6 +104,6 @@ namespace :deploy do    desc "Run db:seed"    task :seed do -    run "cd #{release_path} && RAILS_ENV=#{rails_env} #{rake} db:seed" +    run "cd #{current_path} && RAILS_ENV=#{rails_env} #{rake} db:seed"    end  end diff --git a/config/locales/journey_patterns.en.yml b/config/locales/journey_patterns.en.yml index e5248c29c..9d9bc21e2 100644 --- a/config/locales/journey_patterns.en.yml +++ b/config/locales/journey_patterns.en.yml @@ -6,6 +6,7 @@ en:        vehicle_journeys_count: "Vehicle journeys: %{count}"        vehicle_journey_at_stops: "Vehicle journey at stops"      actions: +      index: "Journey patterns"        new: "Add a new journey_pattern"        edit: "Edit this journey pattern"        destroy: "Remove this journey pattern" diff --git a/config/locales/stop_points.en.yml b/config/locales/stop_points.en.yml index d22d85731..72e138270 100644 --- a/config/locales/stop_points.en.yml +++ b/config/locales/stop_points.en.yml @@ -55,3 +55,4 @@ en:          name: Stop Point          for_boarding: "Pickup"          for_alighting: "Drop off" +        reflex_id: ID diff --git a/config/locales/stop_points.fr.yml b/config/locales/stop_points.fr.yml index d3c873442..71be684f6 100644 --- a/config/locales/stop_points.fr.yml +++ b/config/locales/stop_points.fr.yml @@ -52,6 +52,7 @@ fr:    simple_form:      labels:        stop_point: -        name: Arrêt  +        name: Arrêt          for_boarding: "Montée"          for_alighting: "Descente" +        reflex_id: ID diff --git a/config/routes.rb b/config/routes.rb index 681b36c59..b01bba18d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -256,4 +256,6 @@ ChouetteIhm::Application.routes.draw do    match '/422', to: 'errors#server_error', via: :all, as: 'unprocessable_entity'    match '/500', to: 'errors#server_error', via: :all, as: 'server_error' +  match '/status', to: 'statuses#index', via: :get +  end diff --git a/config/schedule.rb b/config/schedule.rb index 08488c255..0d2a24f31 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -40,9 +40,15 @@ every :day, :at => '4:00 am' do  end  every 5.minutes do +  rake "import:netex_abort_old"    rake "import:notify_parent"  end +every 5.minutes do +  rake "compliance_check_sets:abort_old" +  rake "compliance_check_sets:notify_parent" +end +  every 1.minute do    command "/bin/echo HeartBeat"  end diff --git a/lib/tasks/compliance_check_sets.rb b/lib/tasks/compliance_check_sets.rb new file mode 100644 index 000000000..c53c7f9ed --- /dev/null +++ b/lib/tasks/compliance_check_sets.rb @@ -0,0 +1,11 @@ +namespace :compliance_check_sets do +  desc "Notify parent check sets when children finish" +  task notify_parent: :environment do +    ParentNotifier.new(ComplianceCheckSet).notify_when_finished +  end + +  desc "Mark old unfinished check sets as 'aborted'" +  task abort_old: :environment do +    ComplianceCheckSet.abort_old +  end +end diff --git a/lib/tasks/imports.rake b/lib/tasks/imports.rake index 6bc84acc8..02e32fd3d 100644 --- a/lib/tasks/imports.rake +++ b/lib/tasks/imports.rake @@ -1,6 +1,11 @@  namespace :import do    desc "Notify parent imports when children finish"    task notify_parent: :environment do -    ParentImportNotifier.notify_when_finished +    ParentNotifier.new(Import).notify_when_finished +  end + +  desc "Mark old unfinished Netex imports as 'aborted'" +  task netex_abort_old: :environment do +    NetexImport.abort_old    end  end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb new file mode 100644 index 000000000..8a6db8e28 --- /dev/null +++ b/spec/controllers/statuses_controller_spec.rb @@ -0,0 +1,50 @@ +RSpec.describe StatusesController, :type => :controller do + +  describe "GET index" do +    login_user +    render_views + + +    let(:request){ get :index} +    let(:parsed_response){ JSON.parse response.body } +    it "should be ok" do +      request +      expect(response).to have_http_status 200 +      expect(parsed_response["status"]).to eq "ok" +    end +    context "without blocked object" do +      before do +        create :referential +        create :import +        create :compliance_check_set +        request +      end + +      it "should be ok" do +        expect(response).to have_http_status 200 +        expect(parsed_response["status"]).to eq "ok" +        expect(parsed_response["referentials_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +      end +    end + +    context "with a blocked object" do +      before do +        create :referential, created_at: 5.hours.ago, ready: false +        create :import +        create :compliance_check_set +        request +      end + +      it "should be ko" do +        expect(Referential.blocked.count).to eq 1 +        expect(response).to have_http_status 200 +        expect(parsed_response["status"]).to eq "ko" +        expect(parsed_response["referentials_blocked"]).to eq 1 +        expect(parsed_response["imports_blocked"]).to eq 0 +        expect(parsed_response["imports_blocked"]).to eq 0 +      end +    end +  end +end diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb index 7443d08bc..92a50b924 100644 --- a/spec/factories/chouette_routes.rb +++ b/spec/factories/chouette_routes.rb @@ -19,6 +19,7 @@ FactoryGirl.define do        after(:create) do |route, evaluator|          create_list(:stop_point, evaluator.stop_points_count, route: route)          route.reload +        route.update_checksum!        end        factory :route_with_journey_patterns do diff --git a/spec/factories/compliance_controls/vehicle_journey_control_factories.rb b/spec/factories/compliance_controls/vehicle_journey_control_factories.rb index e8f68cbdf..86a335aba 100644 --- a/spec/factories/compliance_controls/vehicle_journey_control_factories.rb +++ b/spec/factories/compliance_controls/vehicle_journey_control_factories.rb @@ -1,10 +1,12 @@  FactoryGirl.define do    factory :vehicle_journey_control_wating_time, class: 'VehicleJourneyControl::WaitingTime' do +    maximum 10      association :compliance_control_set    end    factory :vehicle_journey_control_delta, class: 'VehicleJourneyControl::Delta' do +    maximum 10      association :compliance_control_set    end diff --git a/spec/models/chouette/footnote_spec.rb b/spec/models/chouette/footnote_spec.rb index fc5e5f306..05f55c2f0 100644 --- a/spec/models/chouette/footnote_spec.rb +++ b/spec/models/chouette/footnote_spec.rb @@ -1,12 +1,12 @@  require 'spec_helper'  describe Chouette::Footnote, type: :model do -  let(:footnote) { create(:footnote) } +  subject { create(:footnote) }    it { should validate_presence_of :line }    describe 'data_source_ref' do      it 'should set default if omitted' do -      expect(footnote.data_source_ref).to eq "DATASOURCEREF_EDITION_BOIV" +      expect(subject.data_source_ref).to eq "DATASOURCEREF_EDITION_BOIV"      end      it 'should not set default if not omitted' do @@ -18,16 +18,16 @@ describe Chouette::Footnote, type: :model do    end    describe 'checksum' do -    it_behaves_like 'checksum support', :footnote +    it_behaves_like 'checksum support'      context '#checksum_attributes' do        it 'should return code and label' do -        expected = [footnote.code, footnote.label] -        expect(footnote.checksum_attributes).to include(*expected) +        expected = [subject.code, subject.label] +        expect(subject.checksum_attributes).to include(*expected)        end        it 'should not return other atrributes' do -        expect(footnote.checksum_attributes).to_not include(footnote.updated_at) +        expect(subject.checksum_attributes).to_not include(subject.updated_at)        end      end    end diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index b5eb9004c..19a74a0e7 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper'  describe Chouette::JourneyPattern, :type => :model do    it { is_expected.to be_versioned } +  subject { create(:journey_pattern) }    describe 'checksum' do -    it_behaves_like 'checksum support', :journey_pattern +    it_behaves_like 'checksum support'    end    # context 'validate minimum stop_points size' do diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb index 26f57eae5..98cb3e358 100644 --- a/spec/models/chouette/route/route_base_spec.rb +++ b/spec/models/chouette/route/route_base_spec.rb @@ -1,8 +1,8 @@  RSpec.describe Chouette::Route, :type => :model do -    subject { create(:route) } +    describe 'checksum' do -    it_behaves_like 'checksum support', :route +    it_behaves_like 'checksum support'    end    it { is_expected.to enumerize(:direction).in(:straight_forward, :backward, :clockwise, :counter_clockwise, :north, :north_west, :west, :south_west, :south, :south_east, :east, :north_east) } diff --git a/spec/models/chouette/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb index 0282cb8b1..bda6bb04a 100644 --- a/spec/models/chouette/routing_constraint_zone_spec.rb +++ b/spec/models/chouette/routing_constraint_zone_spec.rb @@ -11,7 +11,7 @@ describe Chouette::RoutingConstraintZone, type: :model do    it { is_expected.to be_versioned }    describe 'checksum' do -    it_behaves_like 'checksum support', :routing_constraint_zone +    it_behaves_like 'checksum support'    end    describe 'validations' do diff --git a/spec/models/chouette/time_table_period_spec.rb b/spec/models/chouette/time_table_period_spec.rb index cc1a3ae09..e14d38ade 100644 --- a/spec/models/chouette/time_table_period_spec.rb +++ b/spec/models/chouette/time_table_period_spec.rb @@ -10,7 +10,7 @@ describe Chouette::TimeTablePeriod, :type => :model do    it { is_expected.to validate_presence_of :period_end }    describe 'checksum' do -    it_behaves_like 'checksum support', :time_table_period +    it_behaves_like 'checksum support'    end    describe "#overlap" do diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb index 6e5da2c67..fe4bd3cfa 100644 --- a/spec/models/chouette/time_table_spec.rb +++ b/spec/models/chouette/time_table_spec.rb @@ -1048,7 +1048,7 @@ end    # it { is_expected.to validate_uniqueness_of :objectid }    describe 'checksum' do -    it_behaves_like 'checksum support', :time_table +    it_behaves_like 'checksum support'      it "handles newly built dates and periods" do        time_table = build(:time_table) diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index 02306883c..a97559a0c 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -1,10 +1,12 @@  require 'spec_helper'  RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do +  subject { create(:vehicle_journey_at_stop) } +    describe 'checksum' do      let(:at_stop) { build_stubbed(:vehicle_journey_at_stop) } -    it_behaves_like 'checksum support', :vehicle_journey_at_stop +    it_behaves_like 'checksum support'      context '#checksum_attributes' do        it 'should return attributes' do diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index 7279980a3..76e73d9cf 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -1,6 +1,8 @@  require 'spec_helper'  describe Chouette::VehicleJourney, :type => :model do +  subject { create(:vehicle_journey) } +    it { is_expected.to be_versioned }    it { should have_and_belong_to_many(:purchase_windows) } @@ -21,7 +23,7 @@ describe Chouette::VehicleJourney, :type => :model do    end    describe 'checksum' do -    it_behaves_like 'checksum support', :vehicle_journey +    it_behaves_like 'checksum support'      it "changes when a vjas is updated" do        vehicle_journey = create(:vehicle_journey)        expect{vehicle_journey.vehicle_journey_at_stops.last.update_attribute(:departure_time, Time.now)}.to change{vehicle_journey.reload.checksum} diff --git a/spec/models/compliance_check_spec.rb b/spec/models/compliance_check_spec.rb index f83d78c29..ffa59245c 100644 --- a/spec/models/compliance_check_spec.rb +++ b/spec/models/compliance_check_spec.rb @@ -15,4 +15,36 @@ RSpec.describe ComplianceCheck, type: :model do    it { should validate_presence_of :name }    it { should validate_presence_of :code }    it { should validate_presence_of :origin_code } + +  describe ".abort_old" do +    it "changes check sets older than 4 hours to aborted" do +      Timecop.freeze(Time.now) do +        old_check_set = create( +          :compliance_check_set, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        current_check_set = create(:compliance_check_set, status: 'pending') + +        ComplianceCheckSet.abort_old + +        expect(current_check_set.reload.status).to eq('pending') +        expect(old_check_set.reload.status).to eq('aborted') +      end +    end + +    it "doesn't work on check sets with a `finished_status`" do +      Timecop.freeze(Time.now) do +        check_set = create( +          :compliance_check_set, +          status: 'successful', +          created_at: 4.hours.ago - 1.minute +        ) + +        ComplianceCheckSet.abort_old + +        expect(check_set.reload.status).to eq('successful') +      end +    end +  end  end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb index ffb2360c2..8b85f151b 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import_spec.rb @@ -29,6 +29,58 @@ RSpec.describe Import, type: :model do      )    end +  describe ".abort_old" do +    it "changes imports older than 4 hours to aborted" do +      Timecop.freeze(Time.now) do +        old_import = create( +          :workbench_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        current_import = create(:workbench_import, status: 'pending') + +        Import.abort_old + +        expect(current_import.reload.status).to eq('pending') +        expect(old_import.reload.status).to eq('aborted') +      end +    end + +    it "doesn't work on imports with a `finished_status`" do +      Timecop.freeze(Time.now) do +        import = create( +          :workbench_import, +          status: 'successful', +          created_at: 4.hours.ago - 1.minute +        ) + +        Import.abort_old + +        expect(import.reload.status).to eq('successful') +      end +    end + +    it "only works on the caller type" do +      Timecop.freeze(Time.now) do +        workbench_import = create( +          :workbench_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        netex_import = create( +          :netex_import, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) + +        NetexImport.abort_old + +        expect(workbench_import.reload.status).to eq('pending') +        expect(netex_import.reload.status).to eq('aborted') +      end +    end +  end +    describe "#destroy" do      it "must destroy all child imports" do        netex_import = create(:netex_import) diff --git a/spec/services/parent_import_notifier_spec.rb b/spec/services/parent_notifier_spec.rb index 3ab505f88..ecf508fcd 100644 --- a/spec/services/parent_import_notifier_spec.rb +++ b/spec/services/parent_notifier_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe ParentImportNotifier do +RSpec.describe ParentNotifier do    let(:workbench_import) { create(:workbench_import) }    describe ".notify_when_finished" do @@ -20,7 +20,7 @@ RSpec.describe ParentImportNotifier do          expect(netex_import).to receive(:notify_parent)        end -      ParentImportNotifier.notify_when_finished(netex_imports) +      ParentNotifier.new(Import).notify_when_finished(netex_imports)      end      it "doesn't call #notify_parent if its `notified_parent_at` is set" do @@ -33,11 +33,11 @@ RSpec.describe ParentImportNotifier do        expect(netex_import).not_to receive(:notify_parent) -      ParentImportNotifier.notify_when_finished +      ParentNotifier.new(Import).notify_when_finished      end    end -  describe ".imports_pending_notification" do +  describe ".objects_pending_notification" do      it "includes imports with a parent and `notified_parent_at` unset" do        netex_import = create(          :netex_import, @@ -47,7 +47,7 @@ RSpec.describe ParentImportNotifier do        )        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to eq([netex_import])      end @@ -55,7 +55,7 @@ RSpec.describe ParentImportNotifier do        create(:import, parent: nil)        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end @@ -70,7 +70,7 @@ RSpec.describe ParentImportNotifier do        end        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end @@ -83,7 +83,7 @@ RSpec.describe ParentImportNotifier do        )        expect( -        ParentImportNotifier.imports_pending_notification +        ParentNotifier.new(Import).objects_pending_notification        ).to be_empty      end    end diff --git a/spec/support/checksum_support.rb b/spec/support/checksum_support.rb index e02d9f9f3..f8dffb1b7 100644 --- a/spec/support/checksum_support.rb +++ b/spec/support/checksum_support.rb @@ -1,25 +1,23 @@ -shared_examples 'checksum support' do |factory_name| -  let(:instance) { create(factory_name) } - +shared_examples 'checksum support' do    describe '#current_checksum_source' do      let(:attributes) { ['code_value', 'label_value'] } -    let(:seperator)  { ChecksumSupport::SEPARATOR } +    let(:separator)  { ChecksumSupport::SEPARATOR }      let(:nil_value)  { ChecksumSupport::VALUE_FOR_NIL_ATTRIBUTE }      before do -      allow_any_instance_of(instance.class).to receive(:checksum_attributes).and_return(attributes) +      allow_any_instance_of(subject.class).to receive(:checksum_attributes).and_return(attributes)      end -    it 'should separate attribute by seperator' do -      expect(instance.current_checksum_source).to eq("code_value#{seperator}label_value") +    it 'should separate attribute by separator' do +      expect(subject.current_checksum_source).to eq("code_value#{separator}label_value")      end      context 'nil value' do        let(:attributes) { ['code_value', nil] }        it 'should replace nil attributes by default value' do -        source = "code_value#{seperator}#{nil_value}" -        expect(instance.current_checksum_source).to eq(source) +        source = "code_value#{separator}#{nil_value}" +        expect(subject.current_checksum_source).to eq(source)        end      end @@ -27,27 +25,62 @@ shared_examples 'checksum support' do |factory_name|        let(:attributes) { ['code_value', []] }        it 'should convert to nil' do -        source = "code_value#{seperator}#{nil_value}" -        expect(instance.current_checksum_source).to eq(source) +        source = "code_value#{separator}#{nil_value}" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array value' do +      let(:attributes) { [['v1', 'v2', 'v3'], 'code_value'] } + +      it 'should convert to list' do +        source = "v1,v2,v3#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array of array value' do +      let(:attributes) { [[['a1', 'a2', 'a3'], ['b1', 'b2', 'b3']], 'code_value'] } + +      it 'should convert to list' do +        source = "(a1,a2,a3),(b1,b2,b3)#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source) +      end +    end + +    context 'array of array value, with empty array' do +      let(:attributes) { [[['a1', 'a2', 'a3'], []], 'code_value'] } + +      it 'should convert to list' do +        source = "(a1,a2,a3),-#{separator}code_value" +        expect(subject.current_checksum_source).to eq(source)        end      end    end    it 'should save checksum on create' do -    expect(instance.checksum).to_not be_nil +    expect(subject.checksum).to_not be_nil    end    it 'should save checksum_source' do -    expect(instance.checksum_source).to_not be_nil +    expect(subject.checksum_source).to_not be_nil    end    it 'should trigger set_current_checksum_source on save' do -    expect(instance).to receive(:set_current_checksum_source).at_least(:once) -    instance.save +    expect(subject).to receive(:set_current_checksum_source).at_least(:once) +    subject.save    end    it 'should trigger update_checksum on save' do -    expect(instance).to receive(:update_checksum).at_least(:once) -    instance.save +    expect(subject).to receive(:update_checksum).at_least(:once) +    subject.save +  end + +  it "doesn't change the checksum on save if the source hasn't been changed" do +    checksum = subject.checksum + +    subject.save + +    expect(subject.checksum).to eq(checksum)    end  end | 
