specproof 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/eslint.config.mjs DELETED
@@ -1,43 +0,0 @@
1
- import js from "@eslint/js";
2
- import globals from "globals";
3
- import tseslint from "typescript-eslint";
4
- import pluginReact from "eslint-plugin-react";
5
- import pluginReactHooks from "eslint-plugin-react-hooks";
6
-
7
- export default [
8
- {
9
- ignores: ["**/.next/**", "**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**", "example/**"],
10
- },
11
- {
12
- files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
13
- languageOptions: {
14
- globals: {
15
- ...globals.browser,
16
- ...globals.node,
17
- },
18
- },
19
- },
20
- js.configs.recommended,
21
- ...tseslint.configs.recommended,
22
- {
23
- plugins: {
24
- react: pluginReact,
25
- "react-hooks": pluginReactHooks,
26
- },
27
- rules: {
28
- ...pluginReact.configs.recommended.rules,
29
- ...pluginReactHooks.configs.recommended.rules,
30
- // Disable React JSX scope requirement for Next.js
31
- "react/react-in-jsx-scope": "off",
32
- // Disable prop-types for TypeScript projects
33
- "react/prop-types": "off",
34
- // Allow unused variables that start with underscore
35
- "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
36
- },
37
- settings: {
38
- react: {
39
- version: "detect",
40
- },
41
- },
42
- },
43
- ];
@@ -1,88 +0,0 @@
1
- {
2
- "openapi": "3.0.3",
3
- "info": {
4
- "title": "TaskFlow API",
5
- "version": "1.0.0",
6
- "description": "Example API used to demo SpecProof and validate its analyzer. The spec and the tests in example/tests are deliberately out of step in places, so the audit shows every verdict state: proven coverage, gaps, untested operations, and an undocumented status."
7
- },
8
- "tags": [
9
- { "name": "Auth", "description": "Session and token management" },
10
- { "name": "Tasks", "description": "Create and manage tasks" },
11
- { "name": "Projects", "description": "Group tasks into projects" }
12
- ],
13
- "paths": {
14
- "/auth/login": {
15
- "post": {
16
- "operationId": "login",
17
- "summary": "Exchange credentials for a session token",
18
- "tags": ["Auth"],
19
- "responses": {
20
- "200": { "description": "Session token issued" },
21
- "401": { "description": "Invalid credentials" }
22
- }
23
- }
24
- },
25
- "/tasks": {
26
- "get": {
27
- "operationId": "listTasks",
28
- "summary": "List tasks for the authenticated user",
29
- "tags": ["Tasks"],
30
- "responses": {
31
- "200": { "description": "Task list" },
32
- "401": { "description": "Missing or invalid bearer token" }
33
- }
34
- },
35
- "post": {
36
- "operationId": "createTask",
37
- "summary": "Create a task",
38
- "tags": ["Tasks"],
39
- "responses": {
40
- "201": { "description": "Task created" },
41
- "400": { "description": "Malformed request body" },
42
- "401": { "description": "Missing or invalid bearer token" }
43
- }
44
- }
45
- },
46
- "/tasks/{taskId}": {
47
- "get": {
48
- "operationId": "getTask",
49
- "summary": "Fetch a single task",
50
- "tags": ["Tasks"],
51
- "responses": {
52
- "200": { "description": "The task" },
53
- "404": { "description": "No task with this id" }
54
- }
55
- },
56
- "patch": {
57
- "operationId": "updateTask",
58
- "summary": "Update a task's fields",
59
- "tags": ["Tasks"],
60
- "responses": {
61
- "200": { "description": "Updated task" },
62
- "404": { "description": "No task with this id" },
63
- "409": { "description": "Task was modified concurrently" }
64
- }
65
- },
66
- "delete": {
67
- "operationId": "deleteTask",
68
- "summary": "Delete a task",
69
- "tags": ["Tasks"],
70
- "responses": {
71
- "204": { "description": "Task deleted" },
72
- "404": { "description": "No task with this id" }
73
- }
74
- }
75
- },
76
- "/projects": {
77
- "get": {
78
- "operationId": "listProjects",
79
- "summary": "List projects",
80
- "tags": ["Projects"],
81
- "responses": {
82
- "200": { "description": "Project list" },
83
- "401": { "description": "Missing or invalid bearer token" }
84
- }
85
- }
86
- }
87
- }
88
- }
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { api } from "./client";
4
-
5
- describe("POST /auth/login", () => {
6
- it("returns a session token for valid credentials", async () => {
7
- const res = await api.post("/auth/login", {
8
- email: "ada@example.com",
9
- password: "correct horse battery staple",
10
- });
11
-
12
- expect(res.status).toBe(200);
13
- expect(res.body.token).toMatch(/^tk_/);
14
- });
15
-
16
- it("rejects invalid credentials", async () => {
17
- const res = await api.post("/auth/login", {
18
- email: "ada@example.com",
19
- password: "not-the-password",
20
- });
21
-
22
- expect(res.status).toBe(401);
23
- expect(res.body.token).toBeUndefined();
24
- });
25
- });
@@ -1,53 +0,0 @@
1
- // Minimal HTTP client the example tests are written against.
2
- //
3
- // The example suite is fixture data for SpecProof's analyzer — it is excluded
4
- // from this repo's typecheck, lint, and vitest runs and never executes here.
5
- // It exists so the tests read like a real integration suite.
6
-
7
- const BASE_URL = process.env.API_URL ?? "http://localhost:4010";
8
-
9
- export interface ApiResponse {
10
- status: number;
11
- body: Record<string, unknown> & { id?: string; token?: string };
12
- }
13
-
14
- interface RequestOptions {
15
- /** Set to false to send the request without a bearer token */
16
- auth?: boolean;
17
- }
18
-
19
- let sessionToken: string | null = null;
20
-
21
- export function authenticate(token: string | null) {
22
- sessionToken = token;
23
- }
24
-
25
- async function request(
26
- method: string,
27
- path: string,
28
- payload?: unknown,
29
- options: RequestOptions = {},
30
- ): Promise<ApiResponse> {
31
- const withAuth = options.auth !== false && sessionToken !== null;
32
- const res = await fetch(`${BASE_URL}${path}`, {
33
- method,
34
- headers: {
35
- "content-type": "application/json",
36
- ...(withAuth ? { authorization: `Bearer ${sessionToken}` } : {}),
37
- },
38
- body: payload === undefined ? undefined : JSON.stringify(payload),
39
- });
40
- const text = await res.text();
41
- return { status: res.status, body: text ? JSON.parse(text) : {} };
42
- }
43
-
44
- export const api = {
45
- get: (path: string, options?: RequestOptions) =>
46
- request("GET", path, undefined, options),
47
- post: (path: string, payload?: unknown, options?: RequestOptions) =>
48
- request("POST", path, payload, options),
49
- patch: (path: string, payload?: unknown, options?: RequestOptions) =>
50
- request("PATCH", path, payload, options),
51
- delete: (path: string, options?: RequestOptions) =>
52
- request("DELETE", path, undefined, options),
53
- };
@@ -1,83 +0,0 @@
1
- import { beforeAll, describe, expect, it } from "vitest";
2
-
3
- import { api, authenticate } from "./client";
4
-
5
- beforeAll(async () => {
6
- const login = await api.post("/auth/login", {
7
- email: "ada@example.com",
8
- password: "correct horse battery staple",
9
- });
10
- authenticate(login.body.token ?? null);
11
- });
12
-
13
- describe("GET /tasks", () => {
14
- it("lists tasks for the authenticated user", async () => {
15
- const res = await api.get("/tasks");
16
-
17
- expect(res.status).toBe(200);
18
- expect(Array.isArray(res.body.tasks)).toBe(true);
19
- });
20
-
21
- it("rejects requests without a bearer token", async () => {
22
- const res = await api.get("/tasks", { auth: false });
23
-
24
- expect(res.status).toBe(401);
25
- });
26
- });
27
-
28
- describe("POST /tasks", () => {
29
- it("creates a task and returns it", async () => {
30
- const res = await api.post("/tasks", {
31
- title: "Ship the Q3 roadmap",
32
- projectId: "proj_1",
33
- });
34
-
35
- expect(res.status).toBe(201);
36
- expect(res.body.id).toBeDefined();
37
- expect(res.body.title).toBe("Ship the Q3 roadmap");
38
- });
39
-
40
- it("rejects a task without a title", async () => {
41
- const res = await api.post("/tasks", { projectId: "proj_1" });
42
-
43
- expect(res.status).toBe(400);
44
- expect(res.body.error).toBe("title is required");
45
- });
46
-
47
- it("rejects a due date in the past", async () => {
48
- const res = await api.post("/tasks", {
49
- title: "Time travel",
50
- dueDate: "1999-12-31",
51
- });
52
-
53
- expect(res.status).toBe(422);
54
- });
55
- });
56
-
57
- describe("GET /tasks/{taskId}", () => {
58
- it("returns a task by id", async () => {
59
- const created = await api.post("/tasks", { title: "Write release notes" });
60
- const res = await api.get(`/tasks/${created.body.id}`);
61
-
62
- expect(res.status).toBe(200);
63
- expect(res.body.id).toBe(created.body.id);
64
- });
65
-
66
- it("returns 404 for an unknown task id", async () => {
67
- const res = await api.get("/tasks/task_does_not_exist");
68
-
69
- expect(res.status).toBe(404);
70
- });
71
- });
72
-
73
- describe("PATCH /tasks/{taskId}", () => {
74
- it("updates the fields of a task", async () => {
75
- const created = await api.post("/tasks", { title: "Refine the backlog" });
76
- const res = await api.patch(`/tasks/${created.body.id}`, {
77
- title: "Refine and prioritize the backlog",
78
- });
79
-
80
- expect(res.status).toBe(200);
81
- expect(res.body.title).toBe("Refine and prioritize the backlog");
82
- });
83
- });