264 lines
7.3 KiB
JavaScript
264 lines
7.3 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const { refreshApiSource, refreshRssFeed, refreshWeatherLocation } = require('../src/web/lib/data-source-refresh');
|
|
|
|
function createConnection(options) {
|
|
const state = Object.assign({
|
|
rssItemSnapshots: [],
|
|
slideRows: [],
|
|
screenRows: []
|
|
}, options || {});
|
|
const calls = [];
|
|
|
|
return {
|
|
calls,
|
|
async beginTransaction() {
|
|
calls.push(['beginTransaction']);
|
|
},
|
|
async commit() {
|
|
calls.push(['commit']);
|
|
},
|
|
async rollback() {
|
|
calls.push(['rollback']);
|
|
},
|
|
release() {
|
|
calls.push(['release']);
|
|
},
|
|
async query(sql, params) {
|
|
calls.push(['query', sql, params]);
|
|
|
|
if (sql.includes('SELECT item_json')) {
|
|
return [state.rssItemSnapshots.map(function (itemJson) {
|
|
return { item_json: itemJson };
|
|
})];
|
|
}
|
|
|
|
if (sql.includes('SELECT id, content_json FROM c_slides')) {
|
|
return [state.slideRows];
|
|
}
|
|
|
|
if (sql.includes('SELECT DISTINCT s.slug')) {
|
|
return [state.screenRows];
|
|
}
|
|
|
|
return [[]];
|
|
}
|
|
};
|
|
}
|
|
|
|
test('refreshApiSource notifies players only when the API snapshot changes', async () => {
|
|
const connection = createConnection({
|
|
slideRows: [{ id: 10, content_json: JSON.stringify({ source_id: 7 }) }],
|
|
screenRows: [{ slug: 'screen-a' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = {
|
|
async getConnection() {
|
|
return connection;
|
|
}
|
|
};
|
|
const common = {
|
|
async fetchApiSourceResponse() {
|
|
return {
|
|
responseJson: JSON.stringify({ value: 'new' }, null, 2),
|
|
responseStatus: 200,
|
|
responseContentType: 'application/json'
|
|
};
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshApiSource(pool, common, {
|
|
id: 7,
|
|
last_response_json: JSON.stringify({ value: 'old' }, null, 2)
|
|
}, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, [{ slugs: ['screen-a'], payload: 'refresh' }]);
|
|
});
|
|
|
|
test('refreshApiSource skips notifications when the API snapshot is unchanged', async () => {
|
|
const connection = createConnection({
|
|
slideRows: [{ id: 10, content_json: JSON.stringify({ source_id: 7 }) }],
|
|
screenRows: [{ slug: 'screen-a' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = {
|
|
async getConnection() {
|
|
return connection;
|
|
}
|
|
};
|
|
const snapshot = JSON.stringify({ value: 'same' }, null, 2);
|
|
const common = {
|
|
async fetchApiSourceResponse() {
|
|
return {
|
|
responseJson: snapshot,
|
|
responseStatus: 200,
|
|
responseContentType: 'application/json'
|
|
};
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshApiSource(pool, common, {
|
|
id: 7,
|
|
last_response_json: snapshot
|
|
}, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, []);
|
|
});
|
|
|
|
test('refreshRssFeed notifies players only when the RSS items change', async () => {
|
|
const connection = createConnection({
|
|
rssItemSnapshots: [JSON.stringify({ title: 'Old item' })],
|
|
slideRows: [{ id: 11, content_json: JSON.stringify({ feed_id: 9 }) }],
|
|
screenRows: [{ slug: 'screen-b' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = {
|
|
async getConnection() {
|
|
return connection;
|
|
}
|
|
};
|
|
const common = {
|
|
async fetchRssFeedItems() {
|
|
return [{ title: 'New item' }];
|
|
},
|
|
async replaceRssFeedItems() {
|
|
return 1;
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshRssFeed(pool, common, 9, 'https://example.com/feed.xml', 10, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, [{ slugs: ['screen-b'], payload: 'refresh' }]);
|
|
});
|
|
|
|
test('refreshRssFeed skips notifications when the RSS items are unchanged', async () => {
|
|
const snapshot = JSON.stringify({ title: 'Same item' });
|
|
const connection = createConnection({
|
|
rssItemSnapshots: [snapshot],
|
|
slideRows: [{ id: 11, content_json: JSON.stringify({ feed_id: 9 }) }],
|
|
screenRows: [{ slug: 'screen-b' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = {
|
|
async getConnection() {
|
|
return connection;
|
|
}
|
|
};
|
|
const common = {
|
|
async fetchRssFeedItems() {
|
|
return [{ title: 'Same item' }];
|
|
},
|
|
async replaceRssFeedItems() {
|
|
return 1;
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshRssFeed(pool, common, 9, 'https://example.com/feed.xml', 10, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, []);
|
|
});
|
|
|
|
test('refreshWeatherLocation notifies players when the forecast changes', async () => {
|
|
const connection = createConnection({
|
|
slideRows: [{ id: 12, content_json: JSON.stringify({ weather_location_id: 4 }) }],
|
|
screenRows: [{ slug: 'screen-c' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = { async getConnection() { return connection; } };
|
|
const common = {
|
|
async fetchWeatherLocationForecast() {
|
|
return { responseJson: JSON.stringify({ temperature: 21 }) };
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshWeatherLocation(pool, common, {
|
|
id: 4,
|
|
last_pulled_at: new Date(),
|
|
last_response_json: JSON.stringify({ temperature: 20 })
|
|
}, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, [{ slugs: ['screen-c'], payload: 'refresh' }]);
|
|
});
|
|
|
|
test('refreshWeatherLocation skips notifications when the forecast is unchanged within the hour', async () => {
|
|
const connection = createConnection({
|
|
slideRows: [{ id: 12, content_json: JSON.stringify({ weather_location_id: 4 }) }],
|
|
screenRows: [{ slug: 'screen-c' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = { async getConnection() { return connection; } };
|
|
const snapshot = JSON.stringify({ temperature: 20 });
|
|
const common = {
|
|
async fetchWeatherLocationForecast() {
|
|
return { responseJson: snapshot };
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshWeatherLocation(pool, common, {
|
|
id: 4,
|
|
last_pulled_at: new Date(),
|
|
last_response_json: snapshot
|
|
}, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, []);
|
|
});
|
|
|
|
test('refreshWeatherLocation notifies when an unchanged forecast crosses into a new hour', async () => {
|
|
const connection = createConnection({
|
|
slideRows: [{ id: 12, content_json: JSON.stringify({ weather_location_id: 4 }) }],
|
|
screenRows: [{ slug: 'screen-c' }]
|
|
});
|
|
const notifyCalls = [];
|
|
const pool = { async getConnection() { return connection; } };
|
|
const snapshot = JSON.stringify({ temperature: 20 });
|
|
const common = {
|
|
async fetchWeatherLocationForecast() {
|
|
return { responseJson: snapshot };
|
|
},
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
}
|
|
};
|
|
|
|
await refreshWeatherLocation(pool, common, {
|
|
id: 4,
|
|
last_pulled_at: new Date('2000-01-01T00:00:00.000Z'),
|
|
last_response_json: snapshot
|
|
}, 42, async function (slugs, payload) {
|
|
notifyCalls.push({ slugs, payload });
|
|
});
|
|
|
|
assert.deepEqual(notifyCalls, [{ slugs: ['screen-c'], payload: 'refresh' }]);
|
|
});
|