/**
 * Tests that handleCompleteTask rejects path-traversal IDs before any
 * filesystem or DB operation is attempted.
 *
 * Covers the validation-safe-id-path-segments self-feedback fix.
 */

import assert from "node:assert/strict";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
import { describe, it } from 'vitest';
import { handleCompleteTask } from "../tools/complete-task.js";

function makeTmpBase(): string {
	const base = join(tmpdir(), `sf-ct-safe-id-${randomUUID()}`);
	mkdirSync(join(base, ".sf", "milestones", "M001", "slices", "S01", "tasks"), {
		recursive: true,
	});
	return base;
}

const VALID_PARAMS = {
	milestoneId: "M001",
	sliceId: "S01",
	taskId: "T01",
	oneLiner: "Test task",
	narrative: "Did the thing",
	verification: "Checked it",
	deviations: "None.",
	knownIssues: "None.",
	keyFiles: ["src/foo.ts"],
	keyDecisions: ["Used approach A"],
	blockerDiscovered: false,
	verificationEvidence: [],
};

describe("handleCompleteTask — path-traversal ID rejection", () => {
	it("rejects milestoneId containing path traversal (../etc)", async () => {
		const base = makeTmpBase();
		const result = await handleCompleteTask(
			{ ...VALID_PARAMS, milestoneId: "../etc" },
			base,
		);
		assert.ok(
			"error" in result,
			"result should be an error response",
		);
		assert.equal(
			(result as { error: string }).error,
			"unsafe_id",
			'error should be "unsafe_id"',
		);
		assert.equal(
			(result as { field?: string }).field,
			"milestone_id",
			'field should be "milestone_id"',
		);
		assert.ok(
			typeof (result as { reason?: string }).reason === "string" &&
				(result as { reason?: string }).reason!.length > 0,
			"reason should be a non-empty string",
		);
	});

	it("rejects sliceId containing path traversal (S01/../../x)", async () => {
		const base = makeTmpBase();
		const result = await handleCompleteTask(
			{ ...VALID_PARAMS, sliceId: "S01/../../x" },
			base,
		);
		assert.ok(
			"error" in result,
			"result should be an error response",
		);
		assert.equal(
			(result as { error: string }).error,
			"unsafe_id",
			'error should be "unsafe_id"',
		);
		assert.equal(
			(result as { field?: string }).field,
			"slice_id",
			'field should be "slice_id"',
		);
		assert.ok(
			typeof (result as { reason?: string }).reason === "string" &&
				(result as { reason?: string }).reason!.length > 0,
			"reason should be a non-empty string",
		);
	});

	it("rejects taskId containing newline (T01\\nbad)", async () => {
		const base = makeTmpBase();
		const result = await handleCompleteTask(
			{ ...VALID_PARAMS, taskId: "T01\nbad" },
			base,
		);
		assert.ok(
			"error" in result,
			"result should be an error response",
		);
		assert.equal(
			(result as { error: string }).error,
			"unsafe_id",
			'error should be "unsafe_id"',
		);
		assert.equal(
			(result as { field?: string }).field,
			"task_id",
			'field should be "task_id"',
		);
		assert.ok(
			typeof (result as { reason?: string }).reason === "string" &&
				(result as { reason?: string }).reason!.length > 0,
			"reason should be a non-empty string",
		);
	});
});
