Files
air-watcher/test/priceWatcher.test.js
2026-02-19 17:28:58 +09:00

135 lines
3.5 KiB
JavaScript

"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const { PriceWatcher } = require("../src/priceWatcher");
function createSequenceCrawler(prices) {
let index = 0;
return {
async getQuotes() {
const safeIndex = Math.min(index, prices.length - 1);
const price = prices[safeIndex];
index += 1;
return [
{
provider: "sequence-crawler",
price,
currency: "KRW",
},
];
},
};
}
function createSilentLogger() {
return {
log() {},
error() {},
};
}
test("emits threshold alerts when crossing and improving below threshold", async () => {
const notifications = [];
const watcher = new PriceWatcher({
crawler: createSequenceCrawler([1000, 950, 890, 870]),
notifier: {
async notify(event) {
notifications.push(event);
},
},
logger: createSilentLogger(),
});
const watchId = watcher.addWatch({
rawInput: "인천-마드리드 추적",
searchParams: {
segments: [{ from: "ICN", to: "MAD" }],
},
alertRules: {
targetPrice: 900,
notifyOnPriceChange: false,
},
});
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
assert.equal(notifications.length, 2);
assert.equal(notifications[0].eventType, "target_price");
assert.equal(notifications[0].currentBestPrice, 890);
assert.equal(notifications[1].currentBestPrice, 870);
});
test("emits price change alerts when price changes", async () => {
const notifications = [];
const watcher = new PriceWatcher({
crawler: createSequenceCrawler([1000, 980, 980, 950]),
notifier: {
async notify(event) {
notifications.push(event);
},
},
logger: createSilentLogger(),
});
const watchId = watcher.addWatch({
rawInput: "인천-마드리드 추적",
searchParams: {
segments: [{ from: "ICN", to: "MAD" }],
},
alertRules: {
notifyOnPriceChange: true,
targetPrice: null,
},
});
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
await watcher.pollWatch(watchId);
assert.equal(notifications.length, 2);
assert.equal(notifications[0].eventType, "price_changed");
assert.equal(notifications[0].previousBestPrice, 1000);
assert.equal(notifications[0].currentBestPrice, 980);
assert.equal(notifications[1].previousBestPrice, 980);
assert.equal(notifications[1].currentBestPrice, 950);
});
test("keeps crawl snapshot even when notifier fails", async () => {
const watcher = new PriceWatcher({
crawler: createSequenceCrawler([950000]),
notifier: {
async notify() {
throw new Error("telegram timeout");
},
},
logger: createSilentLogger(),
});
const watchId = watcher.addWatch({
rawInput: "인천-마드리드 추적",
searchParams: {
segments: [{ from: "ICN", to: "MAD" }],
},
alertRules: {
targetPrice: 980000,
notifyOnPriceChange: false,
},
});
const result = await watcher.pollWatch(watchId);
const watch = watcher.getWatch(watchId);
assert.equal(result.notificationSent, false);
assert.equal(result.alert.eventType, "target_price");
assert.equal(result.snapshot.bestPrice, 950000);
assert.equal(result.error.phase, "notify");
assert.equal(watch.lastSnapshot.bestPrice, 950000);
assert.equal(watch.lastError.phase, "notify");
});