Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 26, 2026

Three CI jobs were failing: frontend tests couldn't mock @/lib/api, frontend build failed on missing @/lib/toast, and backend tests received 500 errors instead of 200 for non-existent users due to missing database tables.

Frontend Modules

Created missing web/src/lib/ modules:

  • api.ts: API client with specific functions (generatePreview, publishPost, schedulePost) and axios-like interface (get, post) for compatibility with existing code
  • toast.ts: Console-based notification utility with showToast.{success,error,info,warning,loading,dismiss} methods
  • utils.ts: Tailwind class merger (cn) using clsx + tailwind-merge
// web/src/lib/api.ts
export async function generatePreview(request: GeneratePreviewRequest, token?: string) {
  const response = await fetch(`${API_BASE}/api/post/generate-preview`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: JSON.stringify(request),
  });
  return response.json();
}

export const api = { get, post, generatePreview, publishPost, schedulePost };

Backend Error Handling

Modified backend/routes/settings.py to catch "no such table" errors and return default settings with 200 status instead of raising 500:

except Exception as e:
    error_str = str(e).lower()
    if "no such table" in error_str or ("relation" in error_str and "does not exist" in error_str):
        logger.warning(f"Database table not found for user {user_id}, returning default settings")
        return {"user_id": user_id, "github_username": "", "preferences": {}, ...}
    raise HTTPException(status_code=500, detail=str(e))

Other Changes

  • Updated .gitignore to allow web/src/lib/ (was globally ignored by Python's lib/ pattern)
  • Added TypeScript type PostContextInput = string | Record<string, unknown> for better type safety over any

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • fonts.googleapis.com
    • Triggering command: /usr/local/bin/node /usr/local/bin/node /home/REDACTED/work/Post-Bot/Post-Bot/web/node_modules/next/dist/compiled/jest-worker/processChild.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Problem

The CI pipeline has 3 failing jobs:

1. Frontend Tests (web/tests/dashboard.test.tsx)

Error: Could not locate module @/lib/api mapped as:

  • The test file tries to mock @/lib/api on line 29, but this module doesn't exist
  • Missing functions: generatePreview, publishPost, schedulePost

2. Frontend Build (web/src/components/dashboard/BotModePanel.tsx)

Error: Cannot find module '@/lib/toast' or its corresponding type declarations

  • Line 10 imports showToast from @/lib/toast, but this file doesn't exist
  • The component uses toast notifications throughout (success, error, loading, etc.)

3. Backend Tests (tests/test_api.py)

Error: no such table: user_settings

  • Tests expect 200 status code but get 500 Internal Server Error
  • Error message: "no such table: user_settings"
  • Affected tests:
    • test_get_nonexistent_user_settings (line 76)
    • test_settings_endpoint_accepts_user_id (line 86)

Solution Required

1. Create web/src/lib/api.ts

Create an API utility module with the following functions:

  • generatePreview(request, token) - POST to /api/post/generate-preview
  • publishPost(request, token) - POST to /api/publish/full
  • schedulePost(request, token) - POST to /api/scheduled

Include proper TypeScript interfaces for requests and responses.

2. Create web/src/lib/toast.ts

Create a toast notification utility that exports showToast with methods:

  • success(message: string): string
  • error(message: string): string
  • info(message: string): string
  • warning(message: string): string
  • loading(message: string): string
  • dismiss(id: string): void

Use a simple console-based implementation for now (can be upgraded to react-hot-toast later).

3. Fix Backend Settings Route Exception Handling

Update backend/routes/settings.py line 75-77 to gracefully handle missing database table:

  • Catch "no such table" errors specifically
  • Return default empty settings object with 200 status instead of 500 error
  • Maintain existing error handling for other exceptions

The fix should return:

{
    "user_id": user_id,
    "github_username": "",
    "preferences": {},
    "persona": {},
    "onboarding_complete": False
}

Success Criteria

  • All 3 CI jobs pass
  • Frontend tests can mock the API module
  • Frontend build completes without module errors
  • Backend tests receive 200 status for non-existent users
  • No breaking changes to existing functionality

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits January 26, 2026 02:38
Co-authored-by: cliff-de-tech <137389025+cliff-de-tech@users.noreply.github.com>
Co-authored-by: cliff-de-tech <137389025+cliff-de-tech@users.noreply.github.com>
Co-authored-by: cliff-de-tech <137389025+cliff-de-tech@users.noreply.github.com>
Co-authored-by: cliff-de-tech <137389025+cliff-de-tech@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix CI pipeline errors in frontend and backend tests Fix CI failures: create missing frontend modules and handle DB errors gracefully Jan 26, 2026
Copilot AI requested a review from cliff-de-tech January 26, 2026 02:51
@cliff-de-tech cliff-de-tech marked this pull request as ready for review January 26, 2026 03:19
Copilot AI review requested due to automatic review settings January 26, 2026 03:19
@cliff-de-tech cliff-de-tech merged commit 7cc1f47 into main Jan 26, 2026
4 of 6 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes three CI failures by creating missing frontend modules and adding graceful error handling for missing database tables in the backend.

Changes:

  • Created three frontend library modules (api.ts, toast.ts, utils.ts) to resolve import errors in tests and builds
  • Modified backend settings endpoint to return default settings (200 status) instead of errors (500 status) when database tables don't exist
  • Updated .gitignore to allow web/src/lib/ TypeScript files while keeping Python's lib/ directories ignored

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
web/src/lib/api.ts New API client module with fetch-based functions for preview generation, publishing, and scheduling posts, plus axios-compatible wrappers
web/src/lib/toast.ts Console-based toast notification utility with success, error, info, warning, loading, and dismiss methods
web/src/lib/utils.ts Tailwind CSS class merger utility using clsx and tailwind-merge
backend/routes/settings.py Added error handling to catch missing database table errors and return default settings instead of 500 errors
.gitignore Added exception to allow web/src/lib/ TypeScript source files while keeping Python lib/ directories ignored

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +142
/**
* Toast Notification Utility
*
* Provides a simple console-based toast notification system.
* This can be upgraded to use react-hot-toast or another UI library later.
*
* Usage:
* const id = showToast.success('Operation successful!');
* showToast.error('Something went wrong');
* showToast.dismiss(id);
*/

// Simple in-memory storage for toast IDs
let toastIdCounter = 0;
const activeToasts = new Set<string>();

/**
* Generate a unique toast ID
*/
function generateId(): string {
toastIdCounter++;
return `toast-${toastIdCounter}-${Date.now()}`;
}

/**
* Log a toast message to console
*/
function logToast(type: string, message: string, id: string): void {
const timestamp = new Date().toISOString();
const prefix = `[${timestamp}] [TOAST:${type}] [${id}]`;

switch (type) {
case 'success':
console.log(`✓ ${prefix} ${message}`);
break;
case 'error':
console.error(`✗ ${prefix} ${message}`);
break;
case 'warning':
console.warn(`⚠ ${prefix} ${message}`);
break;
case 'info':
console.info(`ℹ ${prefix} ${message}`);
break;
case 'loading':
console.log(`⏳ ${prefix} ${message}`);
break;
default:
console.log(`${prefix} ${message}`);
}
}

/**
* Show a success toast notification
*
* @param message - The success message to display
* @returns The toast ID for dismissal
*/
function success(message: string): string {
const id = generateId();
activeToasts.add(id);
logToast('success', message, id);
return id;
}

/**
* Show an error toast notification
*
* @param message - The error message to display
* @returns The toast ID for dismissal
*/
function error(message: string): string {
const id = generateId();
activeToasts.add(id);
logToast('error', message, id);
return id;
}

/**
* Show an info toast notification
*
* @param message - The info message to display
* @returns The toast ID for dismissal
*/
function info(message: string): string {
const id = generateId();
activeToasts.add(id);
logToast('info', message, id);
return id;
}

/**
* Show a warning toast notification
*
* @param message - The warning message to display
* @returns The toast ID for dismissal
*/
function warning(message: string): string {
const id = generateId();
activeToasts.add(id);
logToast('warning', message, id);
return id;
}

/**
* Show a loading toast notification
*
* @param message - The loading message to display
* @returns The toast ID for dismissal
*/
function loading(message: string): string {
const id = generateId();
activeToasts.add(id);
logToast('loading', message, id);
return id;
}

/**
* Dismiss a toast notification
*
* @param id - The toast ID to dismiss
*/
function dismiss(id: string): void {
if (activeToasts.has(id)) {
activeToasts.delete(id);
console.log(`[TOAST] Dismissed: ${id}`);
}
}

/**
* Toast notification API
*/
export const showToast = {
success,
error,
info,
warning,
loading,
dismiss,
};

export default showToast;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console-based implementation should be replaced with the actual react-hot-toast library that's already installed and imported in _app.tsx. The package.json shows react-hot-toast@2.6.0 is a dependency, and web/src/pages/_app.tsx already imports and renders the Toaster component. Using the real toast library would provide actual UI notifications instead of console logs, which aligns better with the existing setup and user experience expectations.

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +171
/**
* Handle LinkedIn OAuth callback (stub for compatibility)
* This function is imported but not currently used in the codebase.
*
* @deprecated This function is not implemented. Do not call it.
* @returns Promise that logs a warning and resolves
*/
export async function handleLinkedInCallback(): Promise<void> {
console.warn('handleLinkedInCallback is not implemented and should not be called');
return Promise.resolve();
}

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleLinkedInCallback function is marked as deprecated and not implemented, yet it's exported and could be mistakenly used. Since the function is imported in web/src/pages/auth/callback.tsx (line 3) but never actually called, consider removing this stub function entirely to avoid confusion. If backward compatibility is a concern, the deprecation warning is appropriate, but the function seems unnecessary.

Suggested change
/**
* Handle LinkedIn OAuth callback (stub for compatibility)
* This function is imported but not currently used in the codebase.
*
* @deprecated This function is not implemented. Do not call it.
* @returns Promise that logs a warning and resolves
*/
export async function handleLinkedInCallback(): Promise<void> {
console.warn('handleLinkedInCallback is not implemented and should not be called');
return Promise.resolve();
}

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +158
export async function generatePreview(
request: GeneratePreviewRequest,
token?: string
): Promise<GeneratePreviewResponse> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

const response = await fetch(`${API_BASE}/api/post/generate-preview`, {
method: 'POST',
headers,
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`Failed to generate preview: ${response.statusText}`);
}

return response.json();
}

/**
* Publish a post to LinkedIn
*
* @param request - The publish request
* @param token - Optional authentication token
* @returns Promise with the publish result
*/
export async function publishPost(
request: PublishPostRequest,
token?: string
): Promise<PublishPostResponse> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

const response = await fetch(`${API_BASE}/api/publish/full`, {
method: 'POST',
headers,
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`Failed to publish post: ${response.statusText}`);
}

return response.json();
}

/**
* Schedule a post for future publication
*
* @param request - The schedule request
* @param token - Optional authentication token
* @returns Promise with the scheduling result
*/
export async function schedulePost(
request: SchedulePostRequest,
token?: string
): Promise<SchedulePostResponse> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

const response = await fetch(`${API_BASE}/api/scheduled`, {
method: 'POST',
headers,
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`Failed to schedule post: ${response.statusText}`);
}

return response.json();
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The codebase uses axios extensively (seen in BotModePanel.tsx, PersonaSettings.tsx, PostScheduler.tsx, and others), but this new API module uses the native fetch API. This creates inconsistent patterns across the codebase. The axios-like wrapper functions (get, post) are provided for compatibility, but the primary functions (generatePreview, publishPost, schedulePost) use fetch directly. Consider either using axios throughout for consistency, or standardizing on fetch if that's the new direction. The current mixed approach could lead to inconsistent error handling and harder maintenance.

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +252
async function get(url: string, config?: RequestConfig): Promise<{ data: any }> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...config?.headers,
};

let fullUrl = `${API_BASE}${url}`;

// Add query parameters if provided
if (config?.params) {
const params = new URLSearchParams();
Object.entries(config.params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
params.append(key, String(value));
}
});
const queryString = params.toString();
if (queryString) {
fullUrl += `?${queryString}`;
}
}

const response = await fetch(fullUrl, {
method: 'GET',
headers,
});

if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
}

const data = await response.json();
return { data };
}

/**
* Generic HTTP POST request
*
* @param url - The URL to post to
* @param body - The request body
* @param config - Request configuration
* @returns Promise with response object
*/
async function post(url: string, body?: any, config?: RequestConfig): Promise<{ data: any }> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...config?.headers,
};

const response = await fetch(`${API_BASE}${url}`, {
method: 'POST',
headers,
body: body ? JSON.stringify(body) : undefined,
});

if (!response.ok) {
throw new Error(`Failed to post to ${url}: ${response.statusText}`);
}

const data = await response.json();
return { data };
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get and post functions return 'any' in their Promise return type, which bypasses type safety. Consider using generics to allow callers to specify the expected response type, e.g., 'async function get<T = unknown>(url: string, config?: RequestConfig): Promise<{ data: T }>'. This would provide better type safety when using these functions.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +86
# Handle missing database table gracefully
error_str = str(e).lower()
if "no such table" in error_str or ("relation" in error_str and "does not exist" in error_str):
logger.warning(f"Database table not found for user {user_id}, returning default settings")
return {
"user_id": user_id,
"github_username": "",
"preferences": {},
"persona": {},
"onboarding_complete": False
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error detection logic uses lowercase string matching to identify database table errors. While this handles both SQLite ("no such table") and PostgreSQL ("relation...does not exist"), it could potentially match unintended error messages. Consider using more specific database error types or error codes if the database library provides them, rather than string matching. For example, if using asyncpg for PostgreSQL, you could catch specific exception types like UndefinedTableError.

Copilot uses AI. Check for mistakes.
Comment on lines +246 to +252
if (!response.ok) {
throw new Error(`Failed to post to ${url}: ${response.statusText}`);
}

const data = await response.json();
return { data };
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post function has the same error handling issue as the other functions - it loses important error details from the backend response. Consider extracting and including the actual error message from the response body.

Copilot uses AI. Check for mistakes.
*/
interface RequestConfig {
headers?: Record<string, string>;
params?: Record<string, any>;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RequestConfig interface uses 'any' for the params type, which bypasses TypeScript's type checking. Consider using 'unknown' or a more specific type like 'Record<string, string | number | boolean | null | undefined>' to maintain type safety while still being flexible.

Suggested change
params?: Record<string, any>;
params?: Record<string, string | number | boolean | null | undefined>;

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +94
if (!response.ok) {
throw new Error(`Failed to generate preview: ${response.statusText}`);
}

return response.json();
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling throws generic Error objects with only the HTTP status text. This loses important error details that the backend might return in the response body. Consider parsing the error response to extract and include the actual error message from the API. For example: 'const errorData = await response.json(); throw new Error(errorData.detail || errorData.error || response.statusText);'. This would provide more helpful error messages to users and developers.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +126
if (!response.ok) {
throw new Error(`Failed to publish post: ${response.statusText}`);
}

return response.json();
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the generatePreview function, this error handling loses important error details from the backend response. Consider extracting and including the actual error message from the response body to provide more helpful error messages.

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +158
if (!response.ok) {
throw new Error(`Failed to schedule post: ${response.statusText}`);
}

return response.json();
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other API functions, this error handling loses important error details from the backend response. Consider extracting and including the actual error message from the response body to provide more helpful error messages.

Copilot uses AI. Check for mistakes.
@cliff-de-tech cliff-de-tech deleted the copilot/fix-ci-pipeline-errors branch January 26, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants