Disable unavailable subreddits after Reddit 404
This commit is contained in:
1 parent
664aa5c098
commit
dcf57273e2
13 files changed
+153
-12
No files matched your search
@@ -49,6 +49,12 @@ describe('ConfigSchema', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('enables subreddits by default', () => {
|
||||
const result = ConfigSchema.safeParse(minimalConfig({ reddit: { subreddits: ['test'] } }));
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) expect(result.data.reddit.subreddits[0].enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts valid tone values', () => {
|
||||
for (const tone of ['default', 'dominant', 'submissive', 'playful', 'direct', 'gentle', 'kink-friendly', 'teasing']) {
|
||||
const result = ConfigSchema.safeParse({
|
||||
@@ -344,7 +350,7 @@ openrouter:
|
||||
`);
|
||||
|
||||
const config = loadConfig();
|
||||
expect(config.reddit.subreddits).toEqual([{ name: 'test-sub', type: 'image', tone: 'default' }]);
|
||||
expect(config.reddit.subreddits).toEqual([{ name: 'test-sub', enabled: true, type: 'image', tone: 'default' }]);
|
||||
expect(config.reddit.pollIntervalMs).toBe(5000);
|
||||
expect(config.openrouter.apiKey).toBe('yaml-key');
|
||||
});
|
||||
|
||||
+25
-1
@@ -1,6 +1,11 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MonitorService } from '../src/services/monitor.js';
|
||||
import { RedditRateLimitError, type RedditApiClient, type RedditPost } from '../src/reddit/api.js';
|
||||
import {
|
||||
RedditRateLimitError,
|
||||
RedditSubredditUnavailableError,
|
||||
type RedditApiClient,
|
||||
type RedditPost,
|
||||
} from '../src/reddit/api.js';
|
||||
import type { PostsRepository, MessagesRepository } from '../src/database/repositories.js';
|
||||
import { createMockLogger } from '../tests/mockLogger.js';
|
||||
import { getTestConfig } from '../tests/testUtils.js';
|
||||
@@ -87,6 +92,25 @@ describe('MonitorService', () => {
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
it('disables a subreddit when Reddit returns a terminal listing 404', async () => {
|
||||
const redditApi = createMockRedditApi();
|
||||
vi.mocked(redditApi.getNewPosts).mockRejectedValueOnce(new RedditSubredditUnavailableError('banned-sub'));
|
||||
const pollingControl = {
|
||||
disableUnavailableSubreddit: vi.fn(),
|
||||
isSubredditEnabled: vi.fn(() => true),
|
||||
};
|
||||
const config = getTestConfig({ reddit: { subreddits: [{ name: 'banned-sub', type: 'image' }] } });
|
||||
monitor = new MonitorService(
|
||||
redditApi, createMockPostsRepo(), createMockMessagesRepo(), config, logger, 0, pollingControl,
|
||||
);
|
||||
|
||||
await expect(monitor.getUnprocessedPosts()).resolves.toEqual([]);
|
||||
expect(pollingControl.disableUnavailableSubreddit).toHaveBeenCalledWith('banned-sub');
|
||||
expect(logger.logs).toContainEqual(expect.objectContaining({
|
||||
level: 'warn', message: expect.stringContaining('Disabled r/banned-sub: Reddit returned HTTP 404'),
|
||||
}));
|
||||
});
|
||||
|
||||
it('uses shared post history so another account cannot rediscover a post', async () => {
|
||||
const post: RedditPost = {
|
||||
id: 't3_shared', subreddit: 'sub1', author: 'user1', title: 'Post',
|
||||
|
||||
@@ -480,14 +480,29 @@ describe('RedditApiClient', () => {
|
||||
});
|
||||
|
||||
describe('getNewPosts', () => {
|
||||
it('returns empty array for 404 subreddit', async () => {
|
||||
it('throws a permanent-disable signal for a plain 404 subreddit', async () => {
|
||||
const error = { isAxiosError: true, response: { status: 404 } };
|
||||
const mockAxios = { get: vi.fn().mockRejectedValue(error) };
|
||||
vi.mocked(axios.create).mockReturnValue(mockAxios as any);
|
||||
|
||||
const client2 = new RedditApiClient(getTestConfig(), logger);
|
||||
const result = await client2.getNewPosts('nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
await expect(client2.getNewPosts('nonexistent')).rejects.toMatchObject({
|
||||
name: 'RedditSubredditUnavailableError', subreddit: 'nonexistent',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws the same permanent-disable signal for Reddit banned responses', async () => {
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
response: { status: 404, data: { reason: 'banned', message: 'Not Found', error: 404 } },
|
||||
};
|
||||
const mockAxios = { get: vi.fn().mockRejectedValue(error) };
|
||||
vi.mocked(axios.create).mockReturnValue(mockAxios as unknown as AxiosInstance);
|
||||
|
||||
const client2 = new RedditApiClient(getTestConfig(), logger);
|
||||
await expect(client2.getNewPosts('banned-sub')).rejects.toMatchObject({
|
||||
name: 'RedditSubredditUnavailableError', subreddit: 'banned-sub',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty array and explains an initial 403 access denial', async () => {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createConfigBackedSubredditPollingControl } from '../src/services/subreddit-polling-control.js';
|
||||
import { getTestConfig } from './testUtils.js';
|
||||
import { createMockLogger } from './mockLogger.js';
|
||||
|
||||
describe('config-backed subreddit polling control', () => {
|
||||
it('disables and persists only the matched subreddit', () => {
|
||||
const config = getTestConfig({ reddit: { subreddits: [{ name: 'gone', type: 'image' }, { name: 'active', type: 'chat' }] } });
|
||||
const store = { load: vi.fn(), save: vi.fn() };
|
||||
const control = createConfigBackedSubredditPollingControl(config, store, createMockLogger());
|
||||
|
||||
control.disableUnavailableSubreddit('GONE');
|
||||
|
||||
expect(config.reddit.subreddits).toEqual([
|
||||
expect.objectContaining({ name: 'gone', enabled: false }),
|
||||
expect.objectContaining({ name: 'active' }),
|
||||
]);
|
||||
expect(store.save).toHaveBeenCalledWith(config);
|
||||
expect(control.isSubredditEnabled('gone')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user