From 18eba44aff112c9f1387ed8a09735cf692e08485 Mon Sep 17 00:00:00 2001 From: Peter Schilling Date: Tue, 4 Aug 2026 13:15:12 -0700 Subject: [PATCH] Fix document list --issue, which never worked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filter was built as issue: { identifier: { eq: ... } }, but IssueFilter has no identifier field — the comparator for the human identifier is spelled id. Linear rejected the variable during coercion, so every invocation of the flag failed before reaching the resolver, regardless of whether the issue existed. The flag has been broken since the command was introduced. Type the filter local as DocumentFilter instead of any, which turns this class of mistake into a compile error rather than a runtime API rejection; deno check flags the bad field directly. Building the filter as a single annotated expression also drops the deno-lint-ignore and keeps it undefined when neither --project nor --issue is passed, so an unfiltered list still sends no filter. The previous tests here were removed for rendering relative timestamps, which are non-deterministic. The regression test instead goes through --json, which prints raw timestamps, and declares the exact request variables so the mock only matches the correct filter shape — verified by restoring the pre-fix code and watching it fail. --- src/commands/document/document-list.ts | 24 +++---- .../__snapshots__/document-list.test.ts.snap | 30 +++++++++ test/commands/document/document-list.test.ts | 65 +++++++++++++++++-- 3 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/commands/document/document-list.ts b/src/commands/document/document-list.ts index 071cc202..0043c2c4 100644 --- a/src/commands/document/document-list.ts +++ b/src/commands/document/document-list.ts @@ -1,5 +1,6 @@ import { Command } from "@cliffy/command" import { gql } from "../../__codegen__/gql.ts" +import type { DocumentFilter } from "../../__codegen__/graphql.ts" import { getGraphQLClient } from "../../utils/graphql.ts" import { getTimeAgo, padDisplay } from "../../utils/display.ts" import { shouldShowSpinner } from "../../utils/hyperlink.ts" @@ -49,23 +50,14 @@ export const listCommand = new Command() spinner?.start() try { - // Build filter based on options - // deno-lint-ignore no-explicit-any - let filter: any = undefined - - if (project) { - filter = { - ...filter, - project: { slugId: { eq: project } }, + // Build filter based on options. Stays undefined when neither flag is + // passed so the query sends no filter at all. + const filter: DocumentFilter | undefined = project || issue + ? { + project: project ? { slugId: { eq: project } } : undefined, + issue: issue ? { id: { eq: issue.toUpperCase() } } : undefined, } - } - - if (issue) { - filter = { - ...filter, - issue: { identifier: { eq: issue.toUpperCase() } }, - } - } + : undefined const client = getGraphQLClient() const result = await client.request(ListDocuments, { diff --git a/test/commands/document/__snapshots__/document-list.test.ts.snap b/test/commands/document/__snapshots__/document-list.test.ts.snap index 8c15110d..aa44ba58 100644 --- a/test/commands/document/__snapshots__/document-list.test.ts.snap +++ b/test/commands/document/__snapshots__/document-list.test.ts.snap @@ -52,6 +52,36 @@ stderr: "" `; +snapshot[`Document List Command - Filter By Issue JSON Output 1`] = ` +stdout: +'{ + "nodes": [ + { + "id": "doc-2", + "title": "Migration Runbook", + "slugId": "a1c27f6d8e04", + "url": "https://linear.app/test/document/migration-runbook-a1c27f6d8e04", + "updatedAt": "2026-01-20T14:15:00Z", + "project": null, + "issue": { + "identifier": "TC-123", + "title": "Plan the migration" + }, + "creator": { + "name": "Jane Smith" + } + } + ], + "pageInfo": { + "hasNextPage": false, + "endCursor": null + } +} +' +stderr: +"" +`; + snapshot[`Document List Command - Empty Results 1`] = ` stdout: "No documents found. diff --git a/test/commands/document/document-list.test.ts b/test/commands/document/document-list.test.ts index 986afe3a..39949b1c 100644 --- a/test/commands/document/document-list.test.ts +++ b/test/commands/document/document-list.test.ts @@ -15,10 +15,12 @@ await snapshotTest({ }, }) -// NOTE: Tests for "List All Documents", "Filter By Project", and "Filter By Issue" -// have been removed because they display relative timestamps (e.g., "3 days ago") -// which are inherently non-deterministic. The fakeTime solution causes hangs with -// mock servers (see project-list.test.ts for similar issue). +// NOTE: The human-readable table tests for "List All Documents", "Filter By Project", +// and "Filter By Issue" have been removed because they display relative timestamps +// (e.g., "3 days ago") which are inherently non-deterministic. The fakeTime solution +// causes hangs with mock servers (see project-list.test.ts for similar issue). +// Issue filtering is covered below via the --json path, which prints raw timestamps +// and is therefore deterministic. // Test JSON output (uses raw timestamps, not relative - deterministic) await snapshotTest({ @@ -69,6 +71,61 @@ await snapshotTest({ }, }) +// Regression test: --issue must filter on IssueFilter.id, not a non-existent +// `identifier` field. The mock declares the exact request variables, so a wrong +// filter shape matches no mock, falls through to the NO_MOCK_CONFIGURED error and +// fails the test rather than quietly producing different output. +await snapshotTest({ + name: "Document List Command - Filter By Issue JSON Output", + meta: import.meta, + colors: false, + args: ["--issue", "TC-123", "--json"], + denoArgs: commonDenoArgs, + async fn() { + const server = new MockLinearServer([ + { + queryName: "ListDocuments", + variables: { + filter: { issue: { id: { eq: "TC-123" } } }, + first: 50, + }, + response: { + data: { + documents: { + nodes: [ + { + id: "doc-2", + title: "Migration Runbook", + slugId: "a1c27f6d8e04", + url: + "https://linear.app/test/document/migration-runbook-a1c27f6d8e04", + updatedAt: "2026-01-20T14:15:00Z", + project: null, + issue: { identifier: "TC-123", title: "Plan the migration" }, + creator: { name: "Jane Smith" }, + }, + ], + pageInfo: { hasNextPage: false, endCursor: null }, + }, + }, + }, + }, + ]) + + try { + await server.start() + Deno.env.set("LINEAR_GRAPHQL_ENDPOINT", server.getEndpoint()) + Deno.env.set("LINEAR_API_KEY", "Bearer test-token") + + await listCommand.parse() + } finally { + await server.stop() + Deno.env.delete("LINEAR_GRAPHQL_ENDPOINT") + Deno.env.delete("LINEAR_API_KEY") + } + }, +}) + // Test empty results await snapshotTest({ name: "Document List Command - Empty Results",