From 7f5ff7bc8f19bd56313cb5398f1631f6fdbf0d8f Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Tue, 22 Sep 2026 22:39:16 +0100 Subject: [PATCH 1/7] refactor: use the SDK's id validator instead of a local base58 check `@doist/comms-sdk` has exported `isValidUuidV7Base58` since 0.11.1 (July), and it checks the v7 version nibble and variant bits on top of the base58 decode. The local `looksLikeOpaqueCommsId` checked neither, so a 21-char name like `EngineeringDiscussion` decoded to 16 bytes and read as an id. That collision is the only reason `resolveChannelRef` grew a name-first-then-getChannel fallback in #66, so both go: `getDirectChannelId` recognises an opaque id again, which also restores the workspace-agnostic behaviour a bare digit-free channel id had before #66. Co-Authored-By: Claude Opus 5 --- src/lib/refs.test.ts | 86 +++++++++++++------------------------------- src/lib/refs.ts | 68 ++++++++--------------------------- 2 files changed, 39 insertions(+), 115 deletions(-) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index ba1cf8f..305ff43 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -22,8 +22,6 @@ import { extractId, getDirectChannelId, isIdRef, - BASE58_ALPHABET, - looksLikeOpaqueCommsId, looksLikeRawId, parseCommsUrl, parseNumericIdRefs, @@ -374,11 +372,17 @@ describe('getDirectChannelId', () => { expect(getDirectChannelId('Engineering')).toBeNull() }) - it('never treats a bare digit-free token as an id, even one that decodes to 16 bytes', () => { - // Valid base58, 21 characters, decodes to 16 bytes: still a plausible channel name. + it('keeps a name that decodes to 16 bytes a name', () => { + // 21 characters of valid base58 decoding to 16 bytes, so the old local + // check took it for an id. It carries no v7 version nibble, so the SDK + // validator refuses it and the name path keeps it. expect(getDirectChannelId('EngineeringDiscussion')).toBeNull() }) + it('resolves a bare digit-free id', () => { + expect(getDirectChannelId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM') + }) + it('rejects URLs that do not identify a channel', () => { expect(() => getDirectChannelId('https://comms.todoist.com/a/12345/msg/CeRAj1WU3YFhsatbAs43L'), @@ -575,17 +579,7 @@ describe('resolveChannelRef', () => { ) }) - it('falls back to getChannel for a bare digit-free id when no name matches', async () => { - mockChannelLists([createChannel('CeRAj1WU3YFhsTejuePLW', 'Engineering')]) - mockGetChannel.mockResolvedValue(createChannel('CDMDzXhBNCgyQZjkDnqwG', 'Ops')) - - const channel = await resolveChannelRef('CDMDzXhBNCgyQZjkDnqwG', 1) - - expect(channel.id).toBe('CDMDzXhBNCgyQZjkDnqwG') - expect(mockGetChannel).toHaveBeenCalledWith('CDMDzXhBNCgyQZjkDnqwG') - }) - - it('prefers a name match over the id fallback for a token that decodes to 16 bytes', async () => { + it('resolves a name that decodes to 16 bytes by name, never as an id', async () => { mockChannelLists([createChannel('CeRAj1WU3YFhsTejuePLW', 'EngineeringDiscussion')]) const channel = await resolveChannelRef('EngineeringDiscussion', 1) @@ -594,29 +588,13 @@ describe('resolveChannelRef', () => { expect(mockGetChannel).not.toHaveBeenCalled() }) - it.each([ - ['NOT_FOUND', 'Comms could not find that resource: 404.'], - ['INVALID_REF', 'Comms rejected the id: id must be UUIDv7 (version nibble mismatch).'], - ])('keeps CHANNEL_NOT_FOUND when the id fallback fails with %s', async (code, message) => { + it('throws CHANNEL_NOT_FOUND for such a name when nothing matches', async () => { mockChannelLists([]) - mockGetChannel.mockRejectedValue(new CliError(code, message)) await expect(resolveChannelRef('EngineeringDiscussion', 1)).rejects.toMatchObject({ code: 'CHANNEL_NOT_FOUND', }) - // Without this the test passes on an empty name list even with the fallback deleted. - expect(mockGetChannel).toHaveBeenCalledWith('EngineeringDiscussion') - }) - - it('lets any other id-fallback failure through', async () => { - mockChannelLists([]) - mockGetChannel.mockRejectedValue( - new CliError('FORBIDDEN', 'Comms refused this action: 403 Forbidden.'), - ) - - await expect(resolveChannelRef('EngineeringDiscussion', 1)).rejects.toMatchObject({ - code: 'FORBIDDEN', - }) + expect(mockGetChannel).not.toHaveBeenCalled() }) it('throws CHANNEL_NOT_FOUND when no match', async () => { @@ -1053,36 +1031,20 @@ describe('resolveChannelMemberRefs', () => { }) }) -describe('looksLikeOpaqueCommsId', () => { - function base58(bytes: number[]): string { - let value = bytes.reduce((acc, byte) => acc * 256n + BigInt(byte), 0n) - let out = '' - while (value > 0n) { - out = BASE58_ALPHABET[Number(value % 58n)] + out - value /= 58n +describe('opaque-id recognition (delegated to the SDK validator)', () => { + it('accepts real ids and refuses base58 look-alikes', () => { + // Real ids carry the v7 version nibble; the look-alikes below decode to + // 16 bytes but do not, which is the distinction the SDK validator makes + // and the local check used to miss. + for (const id of [ + 'CDMDzXhBNCgyQZjkDnqwG', + 'Cf9TR6CPC2dKQL5fB2EoL', + 'CbjxNkWHJBwcaVkoTCRgM', + ]) { + expect(resolveConversationId(id)).toBe(id) } - const leadingZeros = bytes.findIndex((byte) => byte !== 0) - return '1'.repeat(leadingZeros === -1 ? bytes.length : leadingZeros) + out - } - - it('accepts both length extremes a 16-byte id can encode to', () => { - const longest = base58(Array(16).fill(0xff)) - const leadingZero = base58([0, ...Array(15).fill(0xff)]) - const timestampLed = base58([0x01, 0x90, ...Array(14).fill(0xff)]) - expect(longest).toHaveLength(22) - expect(leadingZero).toHaveLength(22) - expect(timestampLed).toHaveLength(21) - for (const id of [longest, leadingZero, timestampLed]) { - expect(looksLikeOpaqueCommsId(id)).toBe(true) + for (const name of ['EngineeringDiscussion', 'CustomerSuccessLeadership', 'nope']) { + expect(() => resolveConversationId(name)).toThrow(CliError) } }) - - it('rejects 17-byte and 15-byte values of the same length', () => { - // 2^128 is the smallest 17-byte value and still encodes to 22 characters, - // so only the byte-length check can reject it. - const smallest17 = base58([0x01, ...Array(16).fill(0x00)]) - expect(smallest17).toHaveLength(22) - expect(looksLikeOpaqueCommsId(smallest17)).toBe(false) - expect(looksLikeOpaqueCommsId(base58(Array(15).fill(0xff)))).toBe(false) - }) }) diff --git a/src/lib/refs.ts b/src/lib/refs.ts index 6b033c5..19646ff 100644 --- a/src/lib/refs.ts +++ b/src/lib/refs.ts @@ -1,4 +1,10 @@ -import { type Channel, type Group, parseCommsURL, type Workspace } from '@doist/comms-sdk' +import { + type Channel, + type Group, + isValidUuidV7Base58, + parseCommsURL, + type Workspace, +} from '@doist/comms-sdk' import { fetchWorkspaces, getGroup, getWorkspaceGroups, getCommsClient } from './api.js' import { CliError, type ErrorCode, isCliErrorCode } from './errors.js' @@ -72,29 +78,8 @@ export function looksLikeRawId(ref: string): boolean { return /\d/.test(normalized) } -export const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - -/** - * Comms entity ids are 16 bytes, base58-encoded. About 3% of them carry no - * digit, so `looksLikeRawId` misses them; decoding is the only check that - * also keeps a long single-word name a name. Ids are timestamp-led, so they - * encode to 21 characters today and 22 at most (58^22 > 2^128). - */ -export function looksLikeOpaqueCommsId(ref: string): boolean { - if (ref.length < 21 || ref.length > 22) return false - let value = 0n - for (const char of ref) { - const digit = BASE58_ALPHABET.indexOf(char) - if (digit === -1) return false - value = value * 58n + BigInt(digit) - } - const leadingZeroBytes = ref.length - ref.replace(/^1+/, '').length - const byteLength = value === 0n ? 0 : Math.ceil(value.toString(16).length / 2) - return leadingZeroBytes + byteLength === 16 -} - function getOpaqueNameId(parsed: ParsedRef): string | null { - return parsed.type === 'name' && looksLikeOpaqueCommsId(parsed.name) ? parsed.name : null + return parsed.type === 'name' && isValidUuidV7Base58(parsed.name) ? parsed.name : null } export interface ParsedCommsUrl { @@ -322,33 +307,12 @@ export async function resolveChannelRef(ref: string, workspaceId: number): Promi ...joined, ...publicChannels.filter((channel) => !joinedIds.has(channel.id)), ] - try { - return matchByName(channels, parsed.name, { - ambiguousCode: 'AMBIGUOUS_CHANNEL', - notFoundCode: 'CHANNEL_NOT_FOUND', - ref, - listHint: 'Run: tdc channels to list available channels', - }) - } catch (error) { - if ( - !isCliErrorCode(error, 'CHANNEL_NOT_FOUND') || - !looksLikeOpaqueCommsId(parsed.name) - ) { - throw error - } - // Nothing by that name, and the token decodes to a Comms id: a bare - // digit-free channel id lands here rather than in `getDirectChannelId`. - try { - const channel = await client.channels.getChannel(parsed.name) - assertChannelInWorkspace(channel, workspaceId) - return channel - } catch (idError) { - // A miss (404) or a token the server will not take as an id - // (409, "must be UUIDv7") both mean it was a name after all. - if (isCliErrorCode(idError, 'NOT_FOUND', 'INVALID_REF')) throw error - throw idError - } - } + return matchByName(channels, parsed.name, { + ambiguousCode: 'AMBIGUOUS_CHANNEL', + notFoundCode: 'CHANNEL_NOT_FOUND', + ref, + listHint: 'Run: tdc channels to list available channels', + }) } throw new CliError('CHANNEL_NOT_FOUND', `Channel "${ref}" not found`, [ @@ -388,9 +352,7 @@ export function getDirectChannelId(ref: string): string | null { ) } - // A bare digit-free token could be a channel name, so it goes to name - // lookup; `resolveChannelRef` tries it as an id only when no name matches. - return null + return getOpaqueNameId(parsed) } export function resolveCommentId(ref: string): string { From abba5b7a9eed5e73922351a01937c77c7ecf05cb Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 13:49:16 +0100 Subject: [PATCH 2/7] fix: restore the name-then-id fallback for bare channel ids The previous commit moved bare-id recognition into getDirectChannelId and deleted the fallback from resolveChannelRef. Two regressions against 3.4.1: - channel threads, channel members and channel update --workspace call resolveChannelRef directly, so a bare digit-free channel id stopped resolving there at all. - channel archive, delete and update went id-first, through a workspace-agnostic getChannel, where 3.4.1 tried a name in the current workspace first. Back to 3.4.1's order in both places, with the SDK's validator in place of the local check. search and thread create keep resolving bare ids, since resolveChannelId has its own check. Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.test.ts | 61 ++++++++++++++++++++++++++++++++++++++++++-- src/lib/refs.ts | 31 +++++++++++++++++----- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index 305ff43..3f3a3ef 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -379,8 +379,10 @@ describe('getDirectChannelId', () => { expect(getDirectChannelId('EngineeringDiscussion')).toBeNull() }) - it('resolves a bare digit-free id', () => { - expect(getDirectChannelId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM') + it('leaves a bare digit-free id to the name path', () => { + // It could also be a channel name, and a name in the current workspace + // must win; `resolveChannelRef` tries it as an id only when none matches. + expect(getDirectChannelId('CbjxNkWHJBwcaVkoTCRgM')).toBeNull() }) it('rejects URLs that do not identify a channel', () => { @@ -597,6 +599,61 @@ describe('resolveChannelRef', () => { expect(mockGetChannel).not.toHaveBeenCalled() }) + describe('bare digit-free id', () => { + const id = 'CbjxNkWHJBwcaVkoTCRgM' + + it('falls back to getChannel when no name matches', async () => { + mockChannelLists([createChannel('CeRAj1WU3YFhsTejuePLW', 'Engineering')]) + mockGetChannel.mockResolvedValue(createChannel(id, 'CX: Education')) + + const channel = await resolveChannelRef(id, 1) + + expect(channel.id).toBe(id) + expect(mockGetChannel).toHaveBeenCalledWith(id) + }) + + it('prefers a channel with that exact name over the id', async () => { + mockChannelLists([createChannel('CeRAj1WU3YFhsTejuePLW', id)]) + + const channel = await resolveChannelRef(id, 1) + + expect(channel.id).toBe('CeRAj1WU3YFhsTejuePLW') + expect(mockGetChannel).not.toHaveBeenCalled() + }) + + it('refuses an id that belongs to another workspace', async () => { + mockChannelLists([]) + mockGetChannel.mockResolvedValue(createChannel(id, 'Elsewhere', { workspaceId: 2 })) + + await expect(resolveChannelRef(id, 1)).rejects.toMatchObject({ + code: 'CHANNEL_NOT_FOUND', + }) + }) + + it.each([ + ['NOT_FOUND', 'Comms could not find that resource: 404.'], + ['INVALID_REF', 'Comms rejected the id: id must be UUIDv7 (version nibble mismatch).'], + ])('keeps CHANNEL_NOT_FOUND when the id lookup fails with %s', async (code, message) => { + mockChannelLists([]) + mockGetChannel.mockRejectedValue(new CliError(code, message)) + + await expect(resolveChannelRef(id, 1)).rejects.toMatchObject({ + code: 'CHANNEL_NOT_FOUND', + }) + // Without this the test passes on an empty list with no fallback at all. + expect(mockGetChannel).toHaveBeenCalledWith(id) + }) + + it('lets any other id lookup failure through', async () => { + mockChannelLists([]) + mockGetChannel.mockRejectedValue( + new CliError('FORBIDDEN', 'Comms refused this action: 403 Forbidden.'), + ) + + await expect(resolveChannelRef(id, 1)).rejects.toMatchObject({ code: 'FORBIDDEN' }) + }) + }) + it('throws CHANNEL_NOT_FOUND when no match', async () => { mockChannelLists([createChannel('CHGEN', 'General')]) diff --git a/src/lib/refs.ts b/src/lib/refs.ts index 19646ff..5361b4a 100644 --- a/src/lib/refs.ts +++ b/src/lib/refs.ts @@ -307,12 +307,27 @@ export async function resolveChannelRef(ref: string, workspaceId: number): Promi ...joined, ...publicChannels.filter((channel) => !joinedIds.has(channel.id)), ] - return matchByName(channels, parsed.name, { - ambiguousCode: 'AMBIGUOUS_CHANNEL', - notFoundCode: 'CHANNEL_NOT_FOUND', - ref, - listHint: 'Run: tdc channels to list available channels', - }) + try { + return matchByName(channels, parsed.name, { + ambiguousCode: 'AMBIGUOUS_CHANNEL', + notFoundCode: 'CHANNEL_NOT_FOUND', + ref, + listHint: 'Run: tdc channels to list available channels', + }) + } catch (error) { + const opaqueId = getOpaqueNameId(parsed) + if (!opaqueId || !isCliErrorCode(error, 'CHANNEL_NOT_FOUND')) throw error + // No channel by that name, and the token is a valid id: a bare + // digit-free channel id lands here rather than in `getDirectChannelId`. + try { + const channel = await client.channels.getChannel(opaqueId) + assertChannelInWorkspace(channel, workspaceId) + return channel + } catch (idError) { + if (isCliErrorCode(idError, 'NOT_FOUND', 'INVALID_REF')) throw error + throw idError + } + } } throw new CliError('CHANNEL_NOT_FOUND', `Channel "${ref}" not found`, [ @@ -352,7 +367,9 @@ export function getDirectChannelId(ref: string): string | null { ) } - return getOpaqueNameId(parsed) + // A bare digit-free token could be a channel name, so it goes to name + // lookup; `resolveChannelRef` tries it as an id only when no name matches. + return null } export function resolveCommentId(ref: string): string { From c0148ae98c1e9f9283af6a495247c334fcdfee9e Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 13:50:05 +0100 Subject: [PATCH 3/7] test: pin that an ambiguous channel name never falls through to an id Found by mutation: letting the fallback fire on any name failure, not just CHANNEL_NOT_FOUND, passed every test. Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index 3f3a3ef..657e76a 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -621,6 +621,19 @@ describe('resolveChannelRef', () => { expect(mockGetChannel).not.toHaveBeenCalled() }) + it('reports an ambiguous name rather than trying the id', async () => { + mockChannelLists([ + createChannel('CeRAj1WU3YFhsTejuePLW', `${id} one`), + createChannel('Cf9TR6CPC2dKQL5fB2EoL', `${id} two`), + ]) + mockGetChannel.mockResolvedValue(createChannel(id, 'CX: Education')) + + await expect(resolveChannelRef(id, 1)).rejects.toMatchObject({ + code: 'AMBIGUOUS_CHANNEL', + }) + expect(mockGetChannel).not.toHaveBeenCalled() + }) + it('refuses an id that belongs to another workspace', async () => { mockChannelLists([]) mockGetChannel.mockResolvedValue(createChannel(id, 'Elsewhere', { workspaceId: 2 })) From aee5f1ae636fd6c92242dffb23cd3123e02abcd7 Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 13:52:00 +0100 Subject: [PATCH 4/7] chore: keep the comment explaining the id-lookup fallback Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/refs.ts b/src/lib/refs.ts index 5361b4a..4e9e877 100644 --- a/src/lib/refs.ts +++ b/src/lib/refs.ts @@ -324,6 +324,8 @@ export async function resolveChannelRef(ref: string, workspaceId: number): Promi assertChannelInWorkspace(channel, workspaceId) return channel } catch (idError) { + // A miss (404) or a token the server will not take as an id + // (409, "must be UUIDv7") both mean it was a name after all. if (isCliErrorCode(idError, 'NOT_FOUND', 'INVALID_REF')) throw error throw idError } From 551ec7799928300d7eb2a3f7de45fd2f07766ee6 Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 14:08:43 +0100 Subject: [PATCH 5/7] test: keep only the id-vs-look-alike case in the SDK validator block Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.test.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index 657e76a..d6fc3df 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -1102,19 +1102,10 @@ describe('resolveChannelMemberRefs', () => { }) describe('opaque-id recognition (delegated to the SDK validator)', () => { - it('accepts real ids and refuses base58 look-alikes', () => { - // Real ids carry the v7 version nibble; the look-alikes below decode to - // 16 bytes but do not, which is the distinction the SDK validator makes - // and the local check used to miss. - for (const id of [ - 'CDMDzXhBNCgyQZjkDnqwG', - 'Cf9TR6CPC2dKQL5fB2EoL', - 'CbjxNkWHJBwcaVkoTCRgM', - ]) { - expect(resolveConversationId(id)).toBe(id) - } - for (const name of ['EngineeringDiscussion', 'CustomerSuccessLeadership', 'nope']) { - expect(() => resolveConversationId(name)).toThrow(CliError) - } + it('tells a digit-free id from a name that decodes to 16 bytes', () => { + // Both are 21 digit-free base58 characters that decode to 16 bytes, so + // only the v7 version nibble the SDK checks tells them apart. + expect(resolveConversationId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM') + expect(() => resolveConversationId('EngineeringDiscussion')).toThrow(CliError) }) }) From 2ca3c87726d963280124ac85fc05987bf6437236 Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 15:06:18 +0100 Subject: [PATCH 6/7] test: pin bare digit-free ids on the comment and message resolvers Deleting the opaque-id fallback from resolveCommentId or resolveMessageId left the suite green; the other four call sites were already pinned. Also corrects two comments that still described the old decode-only check. Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.test.ts | 8 ++++++++ src/lib/refs.ts | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index d6fc3df..5c01c7c 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -445,6 +445,10 @@ describe('resolveCommentId', () => { ), ).toBe('CeRAj1WU3YFhsY6fUxMhj') }) + + it('resolves generated Comms IDs without digits', () => { + expect(resolveCommentId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM') + }) }) describe('resolveChannelId', () => { @@ -762,6 +766,10 @@ describe('resolveMessageId', () => { ), ).toBe('CeRAj1WU3YFhsbp9GT1ir') }) + + it('resolves generated Comms IDs without digits', () => { + expect(resolveMessageId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM') + }) }) describe('partitionNotifyIds', () => { diff --git a/src/lib/refs.ts b/src/lib/refs.ts index 4e9e877..921ddc4 100644 --- a/src/lib/refs.ts +++ b/src/lib/refs.ts @@ -324,8 +324,8 @@ export async function resolveChannelRef(ref: string, workspaceId: number): Promi assertChannelInWorkspace(channel, workspaceId) return channel } catch (idError) { - // A miss (404) or a token the server will not take as an id - // (409, "must be UUIDv7") both mean it was a name after all. + // A miss (404), or an id the server refuses on a rule the SDK + // does not check (409), both mean it was a name after all. if (isCliErrorCode(idError, 'NOT_FOUND', 'INVALID_REF')) throw error throw idError } @@ -342,7 +342,7 @@ export function resolveChannelId(ref: string): string { if (channelId) return channelId // Id-only, like the thread and conversation resolvers: there is no name - // to protect, so a bare digit-free token that decodes is an id. + // to protect, so a bare digit-free token that is a valid id is an id. const opaqueId = getOpaqueNameId(parseRef(ref)) if (opaqueId) return opaqueId From 5349b9bea7e729e9b809fd11ef9cce906370d94b Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 25 Sep 2026 16:43:23 +0100 Subject: [PATCH 7/7] test: make the cross-workspace fallback test see the fallback Co-Authored-By: Claude Opus 5.5 --- src/lib/refs.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/refs.test.ts b/src/lib/refs.test.ts index 5c01c7c..27b713b 100644 --- a/src/lib/refs.test.ts +++ b/src/lib/refs.test.ts @@ -373,9 +373,8 @@ describe('getDirectChannelId', () => { }) it('keeps a name that decodes to 16 bytes a name', () => { - // 21 characters of valid base58 decoding to 16 bytes, so the old local - // check took it for an id. It carries no v7 version nibble, so the SDK - // validator refuses it and the name path keeps it. + // Every bare digit-free token goes to the name path here; the id-vs-name + // distinction is pinned on `resolveConversationId` below. expect(getDirectChannelId('EngineeringDiscussion')).toBeNull() }) @@ -645,6 +644,7 @@ describe('resolveChannelRef', () => { await expect(resolveChannelRef(id, 1)).rejects.toMatchObject({ code: 'CHANNEL_NOT_FOUND', }) + expect(mockGetChannel).toHaveBeenCalledWith(id) }) it.each([