Add installer, CI, ARM build and test updates
Introduce a one‑line Docker installer and environment examples, add CI and docker validation workflows, and update docs and tests. Key changes: - Add docker/install.sh installer that bootstraps /opt/synapsis, downloads compose files, optionally installs Docker, and generates secrets. - Add top-level .env.example and docker/.env.example entries for shared storage and local development. - Add GitHub Actions CI (ci.yml) with type checks, targeted vitest runs, build and docker-compose validation. - Update existing docker workflow to set up QEMU and build multi‑arch images (amd64, arm64). - Update README and docker/README to use the installer, point to the new repo, adjust local setup instructions, and note shared S3 env vars. - Update docker-compose comments and include shared S3 env variables and defaults. - Update .gitignore to ignore site-work mirrors. - Tests: expand supported bot source types (add brave_news, youtube), increase POST_MAX_LENGTH from 400 to 600, add minimal user handle in mention handler tests, and add mocks & test adjustments in scheduler tests to keep them unit-scoped. These changes simplify installation, add CI coverage (including Docker config validation), enable multi‑arch builds, and align tests with expanded bot source/validation behavior.
This commit is contained in:
@@ -750,11 +750,13 @@ describe('Feature: bot-system, Property 14: Multiple Source Types Per Bot', () =
|
||||
);
|
||||
});
|
||||
|
||||
it('SUPPORTED_SOURCE_TYPES contains all three types', () => {
|
||||
it('SUPPORTED_SOURCE_TYPES contains the core and extended source types', () => {
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('rss');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('reddit');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('news_api');
|
||||
expect(SUPPORTED_SOURCE_TYPES.length).toBe(3);
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('brave_news');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('youtube');
|
||||
expect(SUPPORTED_SOURCE_TYPES.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -433,7 +433,9 @@ describe('Constants', () => {
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('rss');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('reddit');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('news_api');
|
||||
expect(SUPPORTED_SOURCE_TYPES.length).toBe(4);
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('brave_news');
|
||||
expect(SUPPORTED_SOURCE_TYPES).toContain('youtube');
|
||||
expect(SUPPORTED_SOURCE_TYPES.length).toBe(5);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ export const __addBot = (handle: string, userId: string) => {
|
||||
id,
|
||||
userId,
|
||||
handle: handle.toLowerCase(),
|
||||
user: {
|
||||
handle: handle.toLowerCase(),
|
||||
},
|
||||
name: `Bot ${handle}`,
|
||||
personalityConfig: JSON.stringify({
|
||||
systemPrompt: 'Test bot',
|
||||
|
||||
@@ -454,7 +454,7 @@ describe('Feature: bot-system, Property 38: Post Content Validation', () => {
|
||||
|
||||
it('validation constants are properly defined (Requirement 11.5)', async () => {
|
||||
// Verify constants are reasonable
|
||||
expect(POST_MAX_LENGTH).toBe(400);
|
||||
expect(POST_MAX_LENGTH).toBe(600);
|
||||
expect(POST_MIN_LENGTH).toBe(1);
|
||||
expect(MAX_URLS_PER_POST).toBe(5);
|
||||
|
||||
|
||||
@@ -586,6 +586,22 @@ vi.mock('./rateLimiter', () => ({
|
||||
canPost: vi.fn(async () => ({ allowed: true })),
|
||||
}));
|
||||
|
||||
// Mock post creation so scheduler tests stay unit-scoped
|
||||
vi.mock('./posting', () => ({
|
||||
triggerPost: vi.fn(async () => ({
|
||||
success: true,
|
||||
post: {
|
||||
id: 'post-1',
|
||||
content: 'Generated post',
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock source fetching to avoid exercising fetcher behavior here
|
||||
vi.mock('./contentFetcher', () => ({
|
||||
fetchAllSourcesForBot: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
import {
|
||||
hasUnprocessedContent,
|
||||
getNextUnprocessedContent,
|
||||
@@ -742,7 +758,7 @@ describe('processScheduledPosts', () => {
|
||||
expect(result.details[0].status).toBe('skipped_rate_limit');
|
||||
});
|
||||
|
||||
it('should skip bots when no content available', async () => {
|
||||
it('should post original content when a bot has no active sources', async () => {
|
||||
const { db } = await import('@/db');
|
||||
const { canPost } = await import('./rateLimiter');
|
||||
|
||||
@@ -760,6 +776,32 @@ describe('processScheduledPosts', () => {
|
||||
vi.mocked(canPost).mockResolvedValue({ allowed: true });
|
||||
vi.mocked(db.query.botContentSources.findMany).mockResolvedValue([]);
|
||||
|
||||
const result = await processScheduledPosts();
|
||||
expect(result.processed).toBe(1);
|
||||
expect(result.details[0].status).toBe('posted');
|
||||
});
|
||||
|
||||
it('should skip bots when active sources have no content available', async () => {
|
||||
const { db } = await import('@/db');
|
||||
const { canPost } = await import('./rateLimiter');
|
||||
|
||||
vi.mocked(db.query.bots.findMany).mockResolvedValue([
|
||||
{
|
||||
id: 'bot-1',
|
||||
scheduleConfig: JSON.stringify({
|
||||
type: 'interval',
|
||||
intervalMinutes: 30,
|
||||
}),
|
||||
lastPostAt: null,
|
||||
} as any,
|
||||
]);
|
||||
|
||||
vi.mocked(canPost).mockResolvedValue({ allowed: true });
|
||||
vi.mocked(db.query.botContentSources.findMany).mockResolvedValue([
|
||||
{ id: 'source-1' } as any,
|
||||
]);
|
||||
vi.mocked(db.query.botContentItems.findFirst).mockResolvedValue(undefined);
|
||||
|
||||
const result = await processScheduledPosts();
|
||||
expect(result.skipped).toBe(1);
|
||||
expect(result.details[0].status).toBe('skipped_no_content');
|
||||
|
||||
Reference in New Issue
Block a user