135 lines
3.5 KiB
JavaScript
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");
|
|
});
|