Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .env.example

This file was deleted.

21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,26 @@ Assertify is an intelligent testing assistant that analyzes your project descrip
- Next.js 14
- TypeScript
- Tailwind CSS
- OpenAI API
- Multi-LLM Support (OpenAI, Anthropic Claude, Google Gemini)

## Installation

1. Install dependencies: `npm install`
2. Copy the environment file: `cp .env.example .env.local`
3. Add your OpenAI API key to `.env.local`
4. Start the dev server: `npm run dev`
5. Open http://localhost:3000 in your browser
2. Start the dev server: `npm run dev`
3. Open http://localhost:3000 in your browser
4. **Enter your API key in the UI**. Keys are stored in your browser's `sessionStorage` in plaintext for convenience; they are not encrypted or transmitted to our servers. Be aware that browser extensions or any cross-site scripting (XSS) vulnerability in this tab could potentially access this session data.

## How to Use

1. Provide your project description from the landing page; the app automatically classifies the category.
2. Answer the context questions (or skip) so the generator can tailor scenarios to your needs.
3. Review generated tests on the results page, filter by type or priority, and inspect the suggested testing strategy and risk areas.
4. Generate boilerplate code for your preferred frameworks or export the dataset as JSON/CSV.
5. Manage settings at `/settings` to define default context, disable frameworks, and control boilerplate sample sizes.
1. Select your preferred LLM provider (OpenAI, Anthropic, or Gemini) and model from the landing page or settings.
2. Provide your project description from the landing page; the app automatically classifies the category.
3. Answer the context questions (or skip) so the generator can tailor scenarios to your needs.
4. Review generated tests on the results page, filter by type or priority, and inspect the suggested testing strategy and risk areas.
5. Generate boilerplate code for your preferred frameworks or export the dataset as JSON/CSV.
6. Manage settings at `/settings` to define default context, disable frameworks, and control boilerplate sample sizes.

## Future Improvements

- Allow selecting different LLM providers and models per generation so teams can optimize for latency or cost.
- Offer more granular configuration for question generation (e.g., required question count, tone, or domain presets).
- Optimize large test suites by streaming responses and deduplicating similar scenarios before persistence.
- Provide deeper integrations with CI/CD by exporting ready-to-run suites or syncing with test management tools.
Expand Down
51 changes: 26 additions & 25 deletions app/api/classify/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
import { OpenAI } from "openai";
import { createProvider, isValidProviderKey, normalizeProviderError } from "@/lib/llm";

export async function POST(req: NextRequest) {
try {
const { projectDescription, apiKey } = await req.json();
const { projectDescription, apiKey, provider = "openai", model } = await req.json();

if (!projectDescription) {
return NextResponse.json({ error: "Project description required" }, { status: 400 });
Expand All @@ -13,9 +13,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "API key required" }, { status: 400 });
}

const openai = new OpenAI({
apiKey: apiKey,
});
if (!isValidProviderKey(provider)) {
return NextResponse.json({ error: `Unsupported provider: "${provider}"` }, { status: 400 });
}

const llm = createProvider(provider, apiKey, model);

const categories = [
"backend-api",
Expand All @@ -26,39 +28,38 @@ export async function POST(req: NextRequest) {
"data-pipeline",
];

const message = await openai.chat.completions.create({
model: "gpt-4",
messages: [
{
role: "system",
content: `You are a project classifier. Classify the given project description into one of these categories: ${categories.join(
", "
)}. Respond with ONLY the category name, nothing else.`,
},
{
role: "user",
content: projectDescription,
},
],
});
const content = await llm.chatCompletion([
{
role: "system",
content: `You are a project classifier. Classify the given project description into one of these categories: ${categories.join(
", "
)}. Respond with ONLY the category name, nothing else.`,
},
{
role: "user",
content: projectDescription,
},
]);

let category = (message.choices[0].message.content || "other").trim().toLowerCase();
let category = (content || "other").trim().toLowerCase();

if (!categories.includes(category)) {
category = "other";
}

return NextResponse.json({ category });
} catch (error: any) {
} catch (error: unknown) {
console.error("Classification error:", error);

if (error.status === 401) {
const normalized = normalizeProviderError(error);

if (normalized.isAuthError) {
return NextResponse.json({ error: "Unauthorized: Invalid API key" }, { status: 401 });
}

return NextResponse.json(
{ error: "Classification failed", details: String(error.message) },
{ status: 500 }
{ error: "Classification failed", details: normalized.message },
{ status: normalized.status }
);
}
}
34 changes: 18 additions & 16 deletions app/api/generate-questions/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { NextRequest, NextResponse } from "next/server";
import { OpenAI } from "openai";
import { createProvider, isValidProviderKey, normalizeProviderError } from "@/lib/llm";

export async function POST(req: NextRequest) {
try {
const { projectDescription, category, apiKey } = await req.json();
const { projectDescription, category, apiKey, provider = "openai", model } = await req.json();

console.log("Received:", {
projectDescription: !!projectDescription,
category,
provider,
apiKey: !!apiKey,
});

Expand All @@ -22,9 +23,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "API key required" }, { status: 400 });
}

const openai = new OpenAI({
apiKey: apiKey,
});
if (!isValidProviderKey(provider)) {
return NextResponse.json({ error: `Unsupported provider: "${provider}"` }, { status: 400 });
}

const llm = createProvider(provider, apiKey, model);

const prompt = `You are a QA expert. Based on the following project description and category, generate exactly 10 specific, actionable questions that a developer should answer to help write comprehensive tests for this project.

Expand All @@ -42,9 +45,8 @@ Format your response as a JSON array of strings, like this:

Respond ONLY with the JSON array, no additional text or markdown.`;

const message = await openai.chat.completions.create({
model: "gpt-4",
messages: [
const content = await llm.chatCompletion(
[
{
role: "system",
content:
Expand All @@ -55,10 +57,8 @@ Respond ONLY with the JSON array, no additional text or markdown.`;
content: prompt,
},
],
temperature: 0.7,
});

const content = message.choices[0].message.content || "[]";
{ temperature: 0.7 }
);

let questions;
try {
Expand All @@ -84,16 +84,18 @@ Respond ONLY with the JSON array, no additional text or markdown.`;
}

return NextResponse.json({ questions });
} catch (error: any) {
} catch (error: unknown) {
console.error("Generate questions error:", error);

if (error.status === 401) {
const normalized = normalizeProviderError(error);

if (normalized.isAuthError) {
return NextResponse.json({ error: "Unauthorized: Invalid API key" }, { status: 401 });
}

return NextResponse.json(
{ error: "Failed to generate questions", details: String(error.message) },
{ status: 500 }
{ error: "Failed to generate questions", details: normalized.message },
{ status: normalized.status }
);
}
}
40 changes: 24 additions & 16 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import { OpenAI } from "openai";
import { createProvider, isValidProviderKey, normalizeProviderError } from "@/lib/llm";

export async function POST(req: NextRequest) {
try {
const { projectDescription, category, answers, apiKey } = await req.json();
const {
projectDescription,
category,
answers,
apiKey,
provider = "openai",
model,
} = await req.json();

if (!projectDescription || !category || !answers) {
return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
Expand All @@ -13,9 +20,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "API key required" }, { status: 400 });
}

const openai = new OpenAI({
apiKey: apiKey,
});
if (!isValidProviderKey(provider)) {
return NextResponse.json({ error: `Unsupported provider: "${provider}"` }, { status: 400 });
}

const llm = createProvider(provider, apiKey, model);

const prompt = `You are a QA expert. Generate comprehensive test cases for the following project:

Expand Down Expand Up @@ -52,9 +61,8 @@ Generate 12-15 diverse test cases covering:

Respond ONLY with valid JSON, no markdown or extra text.`;

const message = await openai.chat.completions.create({
model: "gpt-4",
messages: [
const content = await llm.chatCompletion(
[
{
role: "system",
content:
Expand All @@ -65,10 +73,8 @@ Respond ONLY with valid JSON, no markdown or extra text.`;
content: prompt,
},
],
temperature: 0.7,
});

const content = message.choices[0].message.content || "{}";
{ temperature: 0.7 }
);

let parsedResponse;
try {
Expand All @@ -83,16 +89,18 @@ Respond ONLY with valid JSON, no markdown or extra text.`;
}

return NextResponse.json(parsedResponse);
} catch (error: any) {
} catch (error: unknown) {
console.error("Generation error:", error);

if (error.status === 401) {
const normalized = normalizeProviderError(error);

if (normalized.isAuthError) {
return NextResponse.json({ error: "Unauthorized: Invalid API key" }, { status: 401 });
}

return NextResponse.json(
{ error: "Test case generation failed", details: String(error.message) },
{ status: 500 }
{ error: "Test case generation failed", details: normalized.message },
{ status: normalized.status }
);
}
}
Loading
Loading