Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #1331 : In scene, add OR condition to check-time action #1425

Merged
merged 3 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions server/lib/scene/scene.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,30 +149,57 @@ const actionsFunc = {
},
[ACTIONS.CONDITION.CHECK_TIME]: async (self, action, scope) => {
const now = dayjs.tz(dayjs(), self.timezone);
let beforeDate;
let afterDate;
let isBeforeCondition = true;
let isAfterCondition = true;

if (action.before) {
const beforeDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.before}`, self.timezone);
const isBeforeCondition = now.isBefore(beforeDate);
beforeDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.before}`, self.timezone);
isBeforeCondition = now.isBefore(beforeDate);
if (!isBeforeCondition) {
logger.debug(
`Check time before: ${now.format('HH:mm')} > ${beforeDate.format('HH:mm')} condition is not verified.`,
`Check time before: ${now.format('HH:mm')} < ${beforeDate.format('HH:mm')} condition is not verified.`,
);
throw new AbortScene('CONDITION_IS_BEFORE_HOUR_NOT_VERIFIED');
} else {
logger.debug(`Check time before: ${now.format('HH:mm')} < ${beforeDate.format('HH:mm')} condition is valid.`);
}
}
if (action.after) {
const afterDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.after}`, self.timezone);
const isAfterCondition = now.isAfter(afterDate);
afterDate = dayjs.tz(`${now.format('YYYY-MM-DD')} ${action.after}`, self.timezone);
isAfterCondition = now.isAfter(afterDate);
if (!isAfterCondition) {
logger.debug(
`Check time after: ${now.format('HH:mm')} > ${afterDate.format('HH:mm')} condition is not verified.`,
);
throw new AbortScene('CONDITION_IS_AFTER_HOUR_NOT_VERIFIED');
} else {
logger.debug(`Check time after: ${now.format('HH:mm')} > ${afterDate.format('HH:mm')} condition is valid.`);
}
}

// if the afterDate is not before the beforeDate
// It means the user is trying to do a cross-day time check
// Example: AFTER 23:00 and BEFORE 8:00.
// This means H > 23 OR h < 8
// Putting a AND has no sense because it'll simply not work
// Example: H > 23 AND H < 8 is always wrong.
if (action.before && action.after && !afterDate.isBefore(beforeDate)) {
// So the condition is a OR in this case
const conditionVerified = isBeforeCondition || isAfterCondition;
if (!conditionVerified) {
throw new AbortScene('CONDITION_BEFORE_OR_AFTER_NOT_VERIFIED');
} else {
logger.debug(`Check time: Condition OR verified.`);
}
} else {
// Otherwise, the condition is a AND
const conditionVerified = isBeforeCondition && isAfterCondition;
if (!conditionVerified) {
throw new AbortScene('CONDITION_BEFORE_AND_AFTER_NOT_VERIFIED');
} else {
logger.debug(`Check time: Condition AND verified.`);
}
}
if (action.days_of_the_week) {
const currentDayOfTheWeek = now.format('dddd').toLowerCase();
const isCurrentDayInCondition = action.days_of_the_week.indexOf(currentDayOfTheWeek) !== -1;
Expand Down
181 changes: 181 additions & 0 deletions server/test/lib/scene/actions/scene.action.checkTime.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
const { assert, fake, useFakeTimers } = require('sinon');
const chaiAssert = require('chai').assert;
const dayjs = require('dayjs');
const EventEmitter = require('events');
const { ACTIONS } = require('../../../../utils/constants');
const { AbortScene } = require('../../../../utils/coreErrors');
const { executeActions } = require('../../../../lib/scene/scene.executeActions');

const StateManager = require('../../../../lib/state');

const event = new EventEmitter();

describe('scene.action.checkTime', () => {
it('should execute condition.check-time, and send message because condition is true', async () => {
const stateManager = new StateManager(event);
const message = {
sendToUser: fake.resolves(null),
};
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const fiveMinutesAgo = todayAt12.subtract(5, 'minute');
const inFiveMinutes = todayAt12.add(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
await executeActions(
{ stateManager, event, message },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: fiveMinutesAgo.format('HH:mm'),
before: inFiveMinutes.format('HH:mm'),
days_of_the_week: [todayAt12.format('dddd').toLowerCase()],
},
],
[
{
type: ACTIONS.MESSAGE.SEND,
user: 'pepper',
text: 'hello',
},
],
],
scope,
);
assert.calledWith(message.sendToUser, 'pepper', 'hello');
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const fiveMinutesAgo = todayAt12.subtract(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
before: fiveMinutesAgo.format('HH:mm'),
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const inFiveMinutes = todayAt12.add(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: inFiveMinutes.format('HH:mm'),
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt19 = dayjs()
.hour(19)
.minute(0);
const eightPm = dayjs()
.hour(20)
.minute(0);
const nineAm = dayjs()
.hour(9)
.minute(0);
const clock = useFakeTimers(todayAt19.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: eightPm.format('HH:mm'),
before: nineAm.format('HH:mm'),
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
it('should not abort scene because condition is verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt20 = dayjs()
.hour(20)
.minute(0);
const sevenPm = dayjs()
.hour(19)
.minute(0);
const nineAm = dayjs()
.hour(9)
.minute(0);
const clock = useFakeTimers(todayAt20.valueOf());
try {
await executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: sevenPm.format('HH:mm'),
before: nineAm.format('HH:mm'),
},
],
],
scope,
);
} catch (e) {
console.log(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected console statement!

throw e;
}
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const tomorrow = todayAt12.add(1, 'day');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
days_of_the_week: [tomorrow.format('dddd').toLowerCase()],
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
});
111 changes: 4 additions & 107 deletions server/test/lib/scene/scene.executeActions.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { assert, fake, useFakeTimers } = require('sinon');
const { assert, fake } = require('sinon');
const chaiAssert = require('chai').assert;
const { expect } = require('chai');
const dayjs = require('dayjs');
const EventEmitter = require('events');
const cloneDeep = require('lodash.clonedeep');
const { ACTIONS } = require('../../../utils/constants');
Expand All @@ -12,6 +11,9 @@ const StateManager = require('../../../lib/state');

const event = new EventEmitter();

// WE ARE SLOWLY MOVING ALL TESTS FROM THIS BIG FILE
// TO A SMALLER SET OF FILE IN THE "ACTIONS" FOLDER.

describe('scene.executeActions', () => {
it('should execute light turn on', async () => {
const device = {
Expand Down Expand Up @@ -619,111 +621,6 @@ describe('scene.executeActions', () => {
);
assert.calledWith(message.sendToUser, 'pepper', 'Temperature in the living room is 15 °C.');
});
it('should execute condition.check-time, and send message because condition is true', async () => {
const stateManager = new StateManager(event);
const message = {
sendToUser: fake.resolves(null),
};
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const fiveMinutesAgo = todayAt12.subtract(5, 'minute');
const inFiveMinutes = todayAt12.add(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
await executeActions(
{ stateManager, event, message },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: fiveMinutesAgo.format('hh:mm'),
before: inFiveMinutes.format('hh:mm'),
days_of_the_week: [todayAt12.format('dddd').toLowerCase()],
},
],
[
{
type: ACTIONS.MESSAGE.SEND,
user: 'pepper',
text: 'hello',
},
],
],
scope,
);
assert.calledWith(message.sendToUser, 'pepper', 'hello');
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const fiveMinutesAgo = todayAt12.subtract(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
before: fiveMinutesAgo.format('hh:mm'),
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const inFiveMinutes = todayAt12.add(5, 'minute');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
after: inFiveMinutes.format('hh:mm'),
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});
it('should abort scene because condition is not verified', async () => {
const stateManager = new StateManager(event);
const scope = {};
const todayAt12 = dayjs()
.hour(12)
.minute(0);
const tomorrow = todayAt12.add(1, 'day');
const clock = useFakeTimers(todayAt12.valueOf());
const promise = executeActions(
{ stateManager, event },
[
[
{
type: ACTIONS.CONDITION.CHECK_TIME,
days_of_the_week: [tomorrow.format('dddd').toLowerCase()],
},
],
],
scope,
);
await chaiAssert.isRejected(promise, AbortScene);
clock.restore();
});

it('should execute action scene.start', async () => {
const stateManager = new StateManager(event);
Expand Down