diff --git a/Gemfile b/Gemfile index 2fd8135ed..a7eb0bf10 100755 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,6 @@ gem "paperclip" gem "figaro" gem "fog-google", git: "https://github.com/fog/fog-google" gem "pg" -gem "montrose" gem "polymorphic_constraints" gem "tzinfo" # For validation of user selected timezone names gem "foreman" diff --git a/Gemfile.lock b/Gemfile.lock index 1e8cb95c0..a251f12a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,8 +153,6 @@ GEM mimemagic (0.3.2) mini_portile2 (2.1.0) minitest (5.10.2) - montrose (0.4.0) - activesupport multi_json (1.12.1) mutations (0.8.1) activesupport @@ -290,7 +288,6 @@ DEPENDENCIES foreman jwt letter_opener - montrose mutations paperclip pg diff --git a/app/controllers/api/tokens_controller.rb b/app/controllers/api/tokens_controller.rb index e785be2d5..5494eb4a9 100644 --- a/app/controllers/api/tokens_controller.rb +++ b/app/controllers/api/tokens_controller.rb @@ -4,10 +4,13 @@ module Api skip_before_action :check_fbos_version, only: :create CREDS = Auth::CreateTokenFromCredentials NO_CREDS = Auth::CreateToken + NO_USER_ATTR = "API requets need a `user` attribute that is a JSON object." def create - klass = (auth_params[:credentials]) ? CREDS : NO_CREDS - mutate klass.run(auth_params).tap{ |result| maybe_halt_login(result) } + if_properly_formatted do |auth_params| + klass = (auth_params[:credentials]) ? CREDS : NO_CREDS + mutate klass.run(auth_params).tap { |result| maybe_halt_login(result) } + end end private @@ -16,14 +19,19 @@ module Api result.result[:user].try(:require_consent!) if result.success? end - def auth_params + def if_properly_formatted user = params.as_json.deep_symbolize_keys.fetch(:user, {}) - - { email: user.fetch(:email, "").downcase, - password: user[:password], - credentials: user[:credentials], - agree_to_terms: !!user[:agree_to_terms], - host: $API_URL } + # If data handling for this method gets any more complicated, + # extract into a mutation. + if(user.is_a?(Hash)) + yield({ email: user.fetch(:email, "").downcase, + password: user[:password], + credentials: user[:credentials], + agree_to_terms: !!user[:agree_to_terms], + host: $API_URL }) + else + render json: {error: NO_USER_ATTR}, status: 422 + end end end end diff --git a/app/mutations/farm_events/generate_calendar.rb b/app/mutations/farm_events/generate_calendar.rb index 9b6cb6cd9..e51120768 100644 --- a/app/mutations/farm_events/generate_calendar.rb +++ b/app/mutations/farm_events/generate_calendar.rb @@ -2,13 +2,12 @@ module FarmEvents # Used to calculate next 60ish occurrences or so of a FarmEvent. class GenerateCalendar < Mutations::Command NEVER = FarmEvent::NEVER.to_s - TIME = { "minutely" => 60, - "hourly" => 60 * 60, - "daily" => 60 * 60 * 24, - "weekly" => 60 * 60 * 24 * 7, - "monthly" => 60 * 60 * 24 * 30, # Not perfect... - "yearly" => 60 * 60 * 24 * 365 } - + TIME = { "minutely" => 60, + "hourly" => 60 * 60, + "daily" => 60 * 60 * 24, + "weekly" => 60 * 60 * 24 * 7, + "monthly" => 60 * 60 * 24 * 30, # Not perfect... + "yearly" => 60 * 60 * 24 * 365 } UNIT_TRANSLATION = { "minutely" => :minutes, "hourly" => :hours, "daily" => :days, @@ -18,11 +17,12 @@ module FarmEvents required do integer :repeat string :time_unit, in: FarmEvent::UNITS_OF_TIME - time :start_time + time :origin + time :lower_limit end optional do - time :end_time + time :upper_limit end def execute @@ -30,35 +30,45 @@ module FarmEvents # Is it in the future? # Then generate a calendar. # Otherwise, return a "partial calendar" that is either empty or (in the - # case of one-off events) has only one date in it (start_time). - (every ? full_calendar : partial_calendar) + # case of one-off events) has only one date in it (origin). + (is_repeating ? full_calendar : partial_calendar) end def full_calendar - throw "NO NO NO!!!" if start_time && end_time && (start_time > end_time) - options = { starts: start_time } - options[:until] = end_time if end_time - return Montrose - .every(every, options) - .take(60) - .reject { |x| end_time ? x > (end_time + 1.second) : false } # clear events beyond the end time - .reject { |x| x <= Time.now } # Clear past events + interval_sec = TIME[time_unit] * repeat + upper = compute_endtime + # How many items must we skip to get to the first occurence? + skip_intervals = ((lower_limit - origin) / interval_sec).ceil + # At what time does the first event occur? + first_item = origin + (skip_intervals * interval_sec).seconds + list = [first_item] + 60.times do + item = list.last + interval_sec.seconds + list.push(item) unless (item >= upper) || (item <= Time.now) + end + + return list end def partial_calendar - in_future? ? [start_time] : [] + in_future? ? [origin] : [] end def the_unit UNIT_TRANSLATION[time_unit] end - def every + def is_repeating (the_unit != NEVER) && the_unit && repeat.send(the_unit) end def in_future? - start_time > Time.now + origin > Time.now + end + + def compute_endtime + next_year = (Time.now + 1.year) + (upper_limit && (upper_limit < next_year)) ? upper_limit : next_year end end end diff --git a/app/serializers/farm_event_serializer.rb b/app/serializers/farm_event_serializer.rb index 6f100967d..028ef90d8 100644 --- a/app/serializers/farm_event_serializer.rb +++ b/app/serializers/farm_event_serializer.rb @@ -5,20 +5,30 @@ class FarmEventSerializer < ActiveModel::Serializer def calendar case object.executable when Sequence then sequence_calendar - # We don't make calendars for Regimens- compute it yourself using - # my_farm_event.executable.regimen_items - RC July 2017 - else [] + when Regimen then regimen_calendar + else throw "Dont know how to calendarize #{exe.class}" end end private + def regimen_calendar + object + .executable + .regimen_items + .pluck(:time_offset) + .map { |x| x / 1000 } + .map { |x| object.start_time.midnight + x } + .select { |x| x > Time.now } || [] + end + def sequence_calendar FarmEvents::GenerateCalendar - .run!(start_time: object.start_time, - end_time: object.end_time, - repeat: object.repeat, - time_unit: object.time_unit) + .run!(origin: object.start_time, + lower_limit: instance_options[:upper_limit] || Time.now, + upper_limit: instance_options[:lower_limit] || object.end_time, + repeat: object.repeat, + time_unit: object.time_unit) .map(&:utc) .map(&:as_json) end diff --git a/public/app-resources/languages/de.js b/public/app-resources/languages/de.js index 35485ea54..4319b5200 100644 --- a/public/app-resources/languages/de.js +++ b/public/app-resources/languages/de.js @@ -1,160 +1,160 @@ module.exports = { - "ACCELERATE FOR (steps)": "BESCHLEUNIGEN FÜR (Schritte)", - "Account Settings": "Konto-Einstellungen", - "Add": "Hinzufügen", - "Add Farm Event": "Farm Event hinzufügen", - "Age": "Alter", - "Agree to Terms of Service": "Den Nutzungsbedingungen zustimmen", - "ALLOW NEGATIVES": "NEGATIVE WERTE ZULASSEN", - "BACK": "ZURÜCK", - "Bot ready": "Bot bereit", - "CALIBRATE {{axis}}": "{{axis}} KALIBRIEREN", - "CALIBRATION": "KALIBRIERUNG", - "calling FarmBot with credentials": "verbinde zu FarmBot mit Zugangsdaten", - "Camera": "Kamera", - "Choose a species": "Wähle eine Art", - "Confirm Password": "Passwort bestätigen", - "CONTROLLER": "CONTROLLER", - "Copy": "Kopieren", - "Could not download sync data": "Konnte Sync-Daten nicht herunterladen", - "Create Account": "Konto anlegen", - "Create An Account": "Ein Konto anlegen", - "Crop Info": "Pflanzen-Info", - "Data Label": "Daten Label", - "Day {{day}}": "Tag {{day}}", - "days old": "Tage alt", - "Delete": "Löschen", - "DELETE ACCOUNT": "KONTO LÖSCHEN", - "Delete this plant": "Diese Pflanze löschen", - "Designer": "Designer", - "DEVICE": "GERÄT", - "downloading device credentials": "Geräte-Zugangsdaten herunterladen", - "Drag and drop into map": "Auf Karte ziehen und fallen lassen", - "DRAG STEP HERE": "Schritt hier fallen lassen", - "Edit": "Bearbeiten", - "EDIT": "BEARBEITEN", - "Edit Farm Event": "Farm-Event bearbeiten", - "Email": "Email", - "ENABLE ENCODERS": "ENCODER AKTIVIEREN", - "Enter Email": "Email eingeben", - "Enter Password": "Passwort eingeben", - "Error establishing socket connection": "Fehler beim Einrichten der Socket-Verbindung", - "Execute Script": "Skript ausführen", - "EXECUTE SCRIPT": "SKRIPT AUSFÜHREN", - "Execute Sequence": "Sequenz ausführen", - "EXECUTE SEQUENCE": "SEQUENZ AUSFÜHREN", - "Factory Reset": "Fabrik-Einstellungen wiederherstellen", - "Farm Events": "Farm-Events", - "FIRMWARE": "FIRMWARE", - "Forgot Password": "Passwort vergessen", - "GO": "Los", - "I Agree to the Terms of Service": "Ich stimme den Nutzungsbedingungen zu", - "I agree to the terms of use": "Ich stimme den Nutzungsbedingungen zu", - "If Statement": "Wenn-Schleife", - "IF STATEMENT": "WENN-SCHLEIFE", - "Import coordinates from": "Koordination importieren von", - "initiating connection": "Verbindung initialisieren", - "INVERT ENDPOINTS": "ENDPUNKTE INVERTIEREN", - "INVERT MOTORS": "MOTOREN INVERTIEREN", - "LENGTH (m)": "LÄNGE (m)", - "Location": "Ort", - "Login": "Login", - "Logout": "Logout", - "Message": "Nachricht", - "Move Absolute": "absolut bewegen", - "MOVE ABSOLUTE": "ABSOLUT BEWEGEN", - "MOVE AMOUNT (mm)": "BETRAG BEWEGEN (mm)", - "Move Relative": "relativ bewegen", - "MOVE RELATIVE": "RELATIV BEWEGEN", - "NAME": "NAME", - "NETWORK": "NETZWERK", - "never connected to device": "noch nie mit Gerät verbunden", - "New Password": "Neues Passwort", - "no": "nein", - "Not Connected to bot": "Nicht mit Bot verbunden", - "Old Password": "Altes Passwort", - "Operator": "Betreiber", - "Package Name": "Packet-Name", - "Parameters": "Parameter", - "Password": "Passwort", - "Pin {{num}}": "Pin {{num}}", - "Pin Mode": "Pin-Modus", - "Pin Number": "Pin-Nummer", - "Plant Info": "Pflanzen-Info", - "Plants": "Pflanzen", - "Problem Loading Terms of Service": "Probleme beim Laden der Nutzungsbedingnungen", - "Read Pin": "Pin lesen", - "READ PIN": "PIN LESEN", - "Regimen Name": "Regimen Name", - "Regimens": "Regimen", - "Repeats Every": "Wiederholt alle", - "Request sent": "Anfrage gesendet", - "Reset": "Zurücksetzen", - "RESET": "ZURÜCKSETZEN", - "Reset Password": "Passwort zurücksetzen", - "Reset your password": "Setze dein Passwort zurück", - "RESTART": "NEUSTART", - "RESTART FARMBOT": "FARMBOT NEUSTARTEN", - "Save": "Seichern", - "SAVE": "SPEICHERN", - "Send Message": "Nachricht senden", - "SEND MESSAGE": "NACHRICHT SENDEN", - "Send Password reset": "Passwort-Zurücksetzen gesendet", - "Sequence": "Sequenz", - "Sequence Editor": "Sequenzen-Editor", - "Sequence or Regimen": "Sequenz oder Regimen", - "Sequences": "Sequenzen", - "Server Port": "Server Port", - "Server URL": "Server URL", - "SHUTDOWN": "Ausschalten", - "SHUTDOWN FARMBOT": "FARMBOT AUSSCHALTEN", - "SLOT": "SLOT", - "Socket Connection Established": "Socket-Verbindung hergestellt", - "Speed": "Geschwindigkeit", - "Started": "Gestartet", - "Starts": "Starts", - "STATUS": "STATUS", - "Steps per MM": "Schritte per MM", - "Sync Required": "Sync benötigt", - "Take a Photo": "Mache ein Foto", - "Take Photo": "Mache Foto", - "TAKE PHOTO": "MACHE FOTO", - "TEST": "TEST", - "Time": "Zeit", - "Time in milliseconds": "Zeit in Millisekunden", - "TIMEOUT AFTER (seconds)": "TIMEOUT NACH (Sekunden)", - "TOOL": "GERÄT", - "TOOL NAME": "GERÄTE-NAMEN", - "TOOLBAY NAME": "GERÄTEHALTER-NAMEN", - "Tried to delete Farm Event": "Versucht Farm-Event zu löschen", - "Tried to delete plant": "Versucht Pflanze zu löschen", - "Tried to save Farm Event": "Versucht Farm-Event zu speichern", - "Tried to save plant": "Versucht Pflanze zu speichern", - "Tried to update Farm Event": "Versucht Farm-Event zu aktualisieren", - "Unable to delete sequence": "Sequenz konnte nicht gelöscht werden", - "Unable to download device credentials": "Geräte-Zugangsdaten konnten nicht heruntergeladen werden", - "Until": "Bis", - "UP TO DATE": "Aktuell", - "UPDATE": "UPDATE", - "Value": "Wert", - "Variable": "Variable", - "Verify Password": "Passwort bestätigen", - "Version": "Version", - "Wait": "Warte", - "WAIT": "WARTE", - "Weed Detector": "Beikraut-Detektor", - "Week": "Wocke", - "Write Pin": "Schreibe Pin", - "WRITE PIN": "SCHREIBE PIN", - "X": "X", - "X (mm)": "X (mm)", - "X AXIS": "X-ACHSE", - "Y": "Y", - "Y (mm)": "Y (mm)", - "Y AXIS": "Y-ACHSE", - "yes": "ya", - "Your Name": "Dein Name", - "Z": "Z", - "Z (mm)": "Z (mm)", - "Z AXIS": "Z-ACHSE" + "ACCELERATE FOR (steps)": "BESCHLEUNIGEN FÜR (Schritte)", + "Account Settings": "Konto-Einstellungen", + "Add": "Hinzufügen", + "Add Farm Event": "Farm Event hinzufügen", + "Age": "Alter", + "Agree to Terms of Service": "Den Nutzungsbedingungen zustimmen", + "ALLOW NEGATIVES": "NEGATIVE WERTE ZULASSEN", + "BACK": "ZURÜCK", + "Bot ready": "Bot bereit", + "CALIBRATE {{axis}}": "{{axis}} KALIBRIEREN", + "CALIBRATION": "KALIBRIERUNG", + "calling FarmBot with credentials": "verbinde zu FarmBot mit Zugangsdaten", + "Camera": "Kamera", + "Choose a species": "Wähle eine Art", + "Confirm Password": "Passwort bestätigen", + "CONTROLLER": "CONTROLLER", + "Copy": "Kopieren", + "Could not download sync data": "Konnte Sync-Daten nicht herunterladen", + "Create Account": "Konto anlegen", + "Create An Account": "Ein Konto anlegen", + "Crop Info": "Pflanzen-Info", + "Data Label": "Daten Label", + "Day {{day}}": "Tag {{day}}", + "days old": "Tage alt", + "Delete": "Löschen", + "DELETE ACCOUNT": "KONTO LÖSCHEN", + "Delete this plant": "Diese Pflanze löschen", + "Designer": "Designer", + "DEVICE": "GERÄT", + "downloading device credentials": "Geräte-Zugangsdaten herunterladen", + "Drag and drop into map": "Auf Karte ziehen und fallen lassen", + "DRAG STEP HERE": "Schritt hier fallen lassen", + "Edit": "Bearbeiten", + "EDIT": "BEARBEITEN", + "Edit Farm Event": "Farm-Event bearbeiten", + "Email": "Email", + "ENABLE ENCODERS": "ENCODER AKTIVIEREN", + "Enter Email": "Email eingeben", + "Enter Password": "Passwort eingeben", + "Error establishing socket connection": "Fehler beim Einrichten der Socket-Verbindung", + "Execute Script": "Skript ausführen", + "EXECUTE SCRIPT": "SKRIPT AUSFÜHREN", + "Execute Sequence": "Sequenz ausführen", + "EXECUTE SEQUENCE": "SEQUENZ AUSFÜHREN", + "Factory Reset": "Fabrik-Einstellungen wiederherstellen", + "Farm Events": "Farm-Events", + "FIRMWARE": "FIRMWARE", + "Forgot Password": "Passwort vergessen", + "GO": "Los", + "I Agree to the Terms of Service": "Ich stimme den Nutzungsbedingungen zu", + "I agree to the terms of use": "Ich stimme den Nutzungsbedingungen zu", + "If Statement": "Wenn-Schleife", + "IF STATEMENT": "WENN-SCHLEIFE", + "Import coordinates from": "Koordination importieren von", + "initiating connection": "Verbindung initialisieren", + "INVERT ENDPOINTS": "ENDPUNKTE INVERTIEREN", + "INVERT MOTORS": "MOTOREN INVERTIEREN", + "LENGTH (m)": "LÄNGE (m)", + "Location": "Ort", + "Login": "Login", + "Logout": "Logout", + "Message": "Nachricht", + "Move Absolute": "absolut bewegen", + "MOVE ABSOLUTE": "ABSOLUT BEWEGEN", + "MOVE AMOUNT (mm)": "BETRAG BEWEGEN (mm)", + "Move Relative": "relativ bewegen", + "MOVE RELATIVE": "RELATIV BEWEGEN", + "NAME": "NAME", + "NETWORK": "NETZWERK", + "never connected to device": "noch nie mit Gerät verbunden", + "New Password": "Neues Passwort", + "no": "nein", + "Not Connected to bot": "Nicht mit Bot verbunden", + "Old Password": "Altes Passwort", + "Operator": "Betreiber", + "Package Name": "Packet-Name", + "Parameters": "Parameter", + "Password": "Passwort", + "Pin {{num}}": "Pin {{num}}", + "Pin Mode": "Pin-Modus", + "Pin Number": "Pin-Nummer", + "Plant Info": "Pflanzen-Info", + "Plants": "Pflanzen", + "Problem Loading Terms of Service": "Probleme beim Laden der Nutzungsbedingnungen", + "Read Pin": "Pin lesen", + "READ PIN": "PIN LESEN", + "Regimen Name": "Regimen Name", + "Regimens": "Regimen", + "Repeats Every": "Wiederholt alle", + "Request sent": "Anfrage gesendet", + "Reset": "Zurücksetzen", + "RESET": "ZURÜCKSETZEN", + "Reset Password": "Passwort zurücksetzen", + "Reset your password": "Setze dein Passwort zurück", + "RESTART": "NEUSTART", + "RESTART FARMBOT": "FARMBOT NEUSTARTEN", + "Save": "Seichern", + "SAVE": "SPEICHERN", + "Send Message": "Nachricht senden", + "SEND MESSAGE": "NACHRICHT SENDEN", + "Send Password reset": "Passwort-Zurücksetzen gesendet", + "Sequence": "Sequenz", + "Sequence Editor": "Sequenzen-Editor", + "Sequence or Regimen": "Sequenz oder Regimen", + "Sequences": "Sequenzen", + "Server Port": "Server Port", + "Server URL": "Server URL", + "SHUTDOWN": "Ausschalten", + "SHUTDOWN FARMBOT": "FARMBOT AUSSCHALTEN", + "SLOT": "SLOT", + "Socket Connection Established": "Socket-Verbindung hergestellt", + "Speed": "Geschwindigkeit", + "Started": "Gestartet", + "Starts": "Starts", + "STATUS": "STATUS", + "Steps per MM": "Schritte per MM", + "Sync Required": "Sync benötigt", + "Take a Photo": "Mache ein Foto", + "Take Photo": "Mache Foto", + "TAKE PHOTO": "MACHE FOTO", + "TEST": "TEST", + "Time": "Zeit", + "Time in milliseconds": "Zeit in Millisekunden", + "TIMEOUT AFTER (seconds)": "TIMEOUT NACH (Sekunden)", + "TOOL": "GERÄT", + "TOOL NAME": "GERÄTE-NAMEN", + "TOOLBAY NAME": "GERÄTEHALTER-NAMEN", + "Tried to delete Farm Event": "Versucht Farm-Event zu löschen", + "Tried to delete plant": "Versucht Pflanze zu löschen", + "Tried to save Farm Event": "Versucht Farm-Event zu speichern", + "Tried to save plant": "Versucht Pflanze zu speichern", + "Tried to update Farm Event": "Versucht Farm-Event zu aktualisieren", + "Unable to delete sequence": "Sequenz konnte nicht gelöscht werden", + "Unable to download device credentials": "Geräte-Zugangsdaten konnten nicht heruntergeladen werden", + "Until": "Bis", + "UP TO DATE": "Aktuell", + "UPDATE": "UPDATE", + "Value": "Wert", + "Variable": "Variable", + "Verify Password": "Passwort bestätigen", + "Version": "Version", + "Wait": "Warte", + "WAIT": "WARTE", + "Weed Detector": "Unkraut-Detektor", + "Week": "Woche", + "Write Pin": "Schreibe Pin", + "WRITE PIN": "SCHREIBE PIN", + "X": "X", + "X (mm)": "X (mm)", + "X AXIS": "X-ACHSE", + "Y": "Y", + "Y (mm)": "Y (mm)", + "Y AXIS": "Y-ACHSE", + "yes": "ya", + "Your Name": "Dein Name", + "Z": "Z", + "Z (mm)": "Z (mm)", + "Z AXIS": "Z-ACHSE" } diff --git a/spec/controllers/api/tokens/tokens_controller_create_spec.rb b/spec/controllers/api/tokens/tokens_controller_create_spec.rb index 03daa532e..5806e40f3 100644 --- a/spec/controllers/api/tokens/tokens_controller_create_spec.rb +++ b/spec/controllers/api/tokens/tokens_controller_create_spec.rb @@ -13,5 +13,12 @@ describe Api::TokensController do expect(token[:iss].last).not_to eq("/") # Trailing slashes are BAD! expect(token[:iss]).to include($API_URL) end + + it 'handles bad params' do + err_msg = Api::TokensController::NO_USER_ATTR + payload = {user: "NOPE!"} + post :create, params: payload + expect(json[:error]).to include(err_msg) + end end end diff --git a/spec/factories/regimen.rb b/spec/factories/regimen.rb index 54856194f..479f8ae7e 100644 --- a/spec/factories/regimen.rb +++ b/spec/factories/regimen.rb @@ -2,5 +2,4 @@ FactoryGirl.define do factory :regimen, :class => 'Regimen' do name { Faker::Pokemon.name + Faker::Pokemon.name} end - end diff --git a/spec/mutations/farm_events/generate_calendar_spec.rb b/spec/mutations/farm_events/generate_calendar_spec.rb index ea2d99aa8..225e3001a 100644 --- a/spec/mutations/farm_events/generate_calendar_spec.rb +++ b/spec/mutations/farm_events/generate_calendar_spec.rb @@ -3,24 +3,26 @@ require 'spec_helper' describe FarmEvents::GenerateCalendar do it 'Builds a list of dates' do start = Time.now + 1.minute - params = { start_time: start, - end_time: start + 1.hours, - repeat: 5, - time_unit: "minutely" } + params = { origin: start, + lower_limit: start, + upper_limit: start + 1.hours, + repeat: 5, + time_unit: "minutely" } calendar = FarmEvents::GenerateCalendar.run!(params) - expect(calendar.first).to eq(params[:start_time]) - calendar.map { |date| expect(date).to be >= params[:start_time] } - calendar.map { |date| expect(date).to be <= params[:end_time] } + expect(calendar.first).to eq(params[:origin]) + calendar.map { |date| expect(date).to be >= params[:origin] } + calendar.map { |date| expect(date).to be <= params[:upper_limit] } expect(calendar.length).to eq(12) end it 'hit a bug in production' do start = Time.now + 1.minute finish = start + 5.days - params = { start_time: start, - end_time: finish, - repeat: 1, - time_unit: "daily" } + params = { origin: start, + lower_limit: start, + upper_limit: finish, + repeat: 1, + time_unit: "daily" } calendar = FarmEvents::GenerateCalendar.run!(params) expect(calendar.first.day).to eq(start.day) expect(calendar.length).to be > 4 @@ -28,36 +30,61 @@ describe FarmEvents::GenerateCalendar do end it 'has a known calendar bug' do - tomorrow = Time.now + 1.day - calendar = FarmEvents::GenerateCalendar.run!( - "start_time" => tomorrow, - "end_time" => tomorrow + 10.hours, - "repeat" => 2, - "time_unit" => "hourly") - + tomorrow = (Time.now + 1.day).midnight + ten_am = tomorrow + 10.hours + calendar = FarmEvents::GenerateCalendar.run!("origin" => tomorrow, + "lower_limit" => tomorrow, + "upper_limit" => ten_am, + "repeat" => 2, + "time_unit" => "hourly") expect(calendar.length).to be > 3 expect(calendar.length).to be < 7 end it 'hit more bugs' do tomorrow = Time.now + 1.day - calendar = FarmEvents::GenerateCalendar.run!("start_time" => tomorrow, - "end_time" => tomorrow + 5.minutes, - "repeat" => 1, - "time_unit" => "minutely") - + calendar = FarmEvents::GenerateCalendar.run!("origin" => tomorrow, + "lower_limit" => tomorrow, + "upper_limit" => tomorrow + 5.minutes, + "repeat" => 1, + "time_unit" => "minutely") expect(calendar.length).to be > 3 expect(calendar.length).to be < 7 end it 'schedules one-off events' do tomorrow = Time.now + 1.day - params = { start_time: tomorrow, - end_time: nil, - repeat: 1, - time_unit: FarmEvent::NEVER } + params = { origin: tomorrow, + lower_limit: tomorrow, + upper_limit: nil, + repeat: 1, + time_unit: FarmEvent::NEVER } calendar = FarmEvents::GenerateCalendar.run!(params) expect(calendar.length).to eq(1) - expect(calendar.first).to eq(params[:start_time]) + expect(calendar.first).to eq(params[:origin]) + end + + idea = ->(start, interval_sec, lower, upper = (Time.now + 1.year)) { + # How many items must we skip to get to the first occurence? + skip_intervals = ((lower - start) / interval_sec).ceil + # At what time does the first event occur? + first_item = start + (skip_intervals * interval_sec).seconds + list = [first_item] + 60.times do + item = list.last + interval_sec.seconds + list.push(item) unless item > upper + end + return list + } + + it 'trys new idea' do + monday = (Time.now - 14.days).monday.midnight + 8.hours # 8am Monday + tuesday = monday + 19.hours # 3am Tuesday + thursday = (monday + 3.days) + 10.hours # 18pm Thursday + interval = 4 * FarmEvents::GenerateCalendar::TIME["hourly"] + result1 = idea[monday, interval, tuesday, thursday] + expect(result1[0].tuesday?).to be(true) + expect(result1[0].hour).to be(4) + expect(result1.length).to be(16) end end diff --git a/spec/serializers/farm_event_serializer_spec.rb b/spec/serializers/farm_event_serializer_spec.rb new file mode 100644 index 000000000..b5f5587dc --- /dev/null +++ b/spec/serializers/farm_event_serializer_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +describe FarmEventSerializer do + let(:farm_event) do + fe = FactoryGirl.build(:farm_event, start_time: Time.now + 5.days) + fe.executable = FactoryGirl.build(:regimen, device: fe.device) + fe.save! + FactoryGirl.create(:regimen_item, regimen: fe.executable, + time_offset: 7000) + fe + end + + it "renders a regimen" do + result = FarmEventSerializer.new(farm_event).as_json + cal = result[:calendar] + expect(cal.length).to be(1) + expect(cal.first).to eq(farm_event.start_time.midnight + 7.seconds) + end + + it "does not render `nil` and friends" do + farm_event.executable = nil + expect{ FarmEventSerializer.new(farm_event).as_json }.to raise_error + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3762ba12d..9ab901bf3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,9 +9,12 @@ SimpleCov.start do end require 'codecov' -SimpleCov.formatter = SimpleCov::Formatter::Codecov +SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::Codecov, +]) require 'pry' -# This file is copied to spec/ when you run 'rails generate rspec:install' + ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/rails'