Skip to content

Commit

Permalink
Add tests for not-quite-empty <title> tags in feeds
Browse files Browse the repository at this point in the history
  • Loading branch information
tadzik committed Apr 12, 2023
1 parent cf2162f commit c481fdb
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 4 deletions.
16 changes: 12 additions & 4 deletions src/feeds/FeedReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ interface AccountData {
[url: string]: string[],
}

interface AccountDataStore {
getAccountData<T>(type: string): Promise<T>;
setAccountData<T>(type: string, data: T): Promise<void>;
}

const accountDataSchema = {
type: 'object',
patternProperties: {
Expand Down Expand Up @@ -127,8 +132,9 @@ export class FeedReader {
headers: Record<string, string>,
timeoutMs: number,
parser: Parser = FeedReader.buildParser(),
httpClient = axios,
): Promise<{ response: AxiosResponse, feed: Parser.Output<FeedItem> }> {
const response = await axios.get(url, {
const response = await httpClient.get(url, {
headers: {
'User-Agent': UserAgent,
...headers,
Expand Down Expand Up @@ -192,7 +198,8 @@ export class FeedReader {
private readonly config: BridgeConfigFeeds,
private readonly connectionManager: ConnectionManager,
private readonly queue: MessageQueue,
private readonly matrixClient: MatrixClient,
private readonly accountDataStore: AccountDataStore,
private readonly httpClient = axios,
) {
this.connections = this.connectionManager.getAllConnectionsOfType(FeedConnection);
this.calculateFeedUrls();
Expand Down Expand Up @@ -241,7 +248,7 @@ export class FeedReader {

private async loadSeenEntries(): Promise<void> {
try {
const accountData = await this.matrixClient.getAccountData<AccountData>(FeedReader.seenEntriesEventType).catch((err: MatrixError|unknown) => {
const accountData = await this.accountDataStore.getAccountData<AccountData>(FeedReader.seenEntriesEventType).catch((err: MatrixError|unknown) => {
if (err instanceof MatrixError && err.statusCode === 404) {
return {} as AccountData;
} else {
Expand All @@ -266,7 +273,7 @@ export class FeedReader {
for (const [url, guids] of this.seenEntries.entries()) {
accountData[url.toString()] = guids;
}
await this.matrixClient.setAccountData(FeedReader.seenEntriesEventType, accountData);
await this.accountDataStore.setAccountData(FeedReader.seenEntriesEventType, accountData);
}

/**
Expand All @@ -292,6 +299,7 @@ export class FeedReader {
// We don't want to wait forever for the feed.
this.config.pollTimeoutSeconds * 1000,
this.parser,
this.httpClient,
);

// Store any entity tags/cache times.
Expand Down
89 changes: 89 additions & 0 deletions tests/FeedReader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { AxiosResponse, AxiosStatic } from "axios";
import { expect } from "chai";
import EventEmitter from "events";
import { BridgeConfigFeeds } from "../src/Config/Config";
import { ConnectionManager } from "../src/ConnectionManager";
import { IConnection } from "../src/Connections";
import { FeedReader } from "../src/feeds/FeedReader";
import { MessageQueue, MessageQueueMessage } from "../src/MessageQueue";

class MockConnectionManager extends EventEmitter {
constructor(
public connections: IConnection[]
) {
super();
}

getAllConnectionsOfType(type: unknown) {
return this.connections;
}
}

class MockMessageQueue extends EventEmitter implements MessageQueue {
subscribe(eventGlob: string): void {
this.emit('subscribed', eventGlob);
}

unsubscribe(eventGlob: string): void {
this.emit('unsubscribed', eventGlob);
}

async push(data: MessageQueueMessage<unknown>, single?: boolean): Promise<void> {
this.emit('pushed', data, single);
}

async pushWait<T, X>(data: MessageQueueMessage<T>, timeout?: number, single?: boolean): Promise<X> {
throw new Error('Not yet implemented');
}
}

class MockHttpClient {
constructor(public response: AxiosResponse) {}

get(): Promise<AxiosResponse> {
return Promise.resolve(this.response);
}
}

describe("FeedReader", () => {
it("should correctly handle empty titles", async () => {
const config = new BridgeConfigFeeds({
enabled: true,
pollIntervalSeconds: 1,
pollTimeoutSeconds: 1,
});
const cm = new MockConnectionManager([{ feedUrl: 'http://test/' } as unknown as IConnection]) as unknown as ConnectionManager
const mq = new MockMessageQueue();

const feedContents = `
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel><title type='text'></title><description>test feed</description><link>http://test/</link>
<pubDate>Wed, 12 Apr 2023 09:53:00 GMT</pubDate>
<item>
<title type='text'></title><description>test item</description>
<link>http://example.com/test/1681293180</link>
<guid isPermaLink="true">http://example.com/test/1681293180</guid>
<pubDate>Wed, 12 Apr 2023 09:53:00 GMT</pubDate>
</item>
</channel></rss>
`;

const feedReader = new FeedReader(
config, cm, mq,
{
getAccountData: <T>() => Promise.resolve({ 'http://test/': [] } as unknown as T),
setAccountData: <T>() => Promise.resolve(),
},
new MockHttpClient({ headers: {}, data: feedContents } as AxiosResponse) as unknown as AxiosStatic,
);

const event: any = await new Promise((resolve) => {
mq.on('pushed', (data, _) => { resolve(data); feedReader.stop() });
});

expect(event.eventName).to.equal('feed.entry');
expect(event.data.feed.title).to.equal(null);
expect(event.data.title).to.equal(null);
});
});

0 comments on commit c481fdb

Please sign in to comment.