-
-
Notifications
You must be signed in to change notification settings - Fork 242
refactor(4/12): extract build/test utilities, platform steps, and xcodebuild pipeline #322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cameroncooke
wants to merge
5
commits into
main
Choose a base branch
from
refactor/build-test-utility-extraction
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
5fa2c95
refactor: extract build/test utilities, platform steps, and xcodebuil…
cameroncooke 9c29edc
fix: add 30s TTL to device name resolver cache
cameroncooke bee0b39
refactor: add shared runLogic test helper
cameroncooke 76c9848
fix: address PR review feedback, restore responses barrel, update DI …
cameroncooke d279a9b
fix: add newline separator before next steps section in processToolRe…
cameroncooke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| /** | ||
| * Shared test helpers for extracting text content from tool responses. | ||
| */ | ||
|
|
||
| import { expect } from 'vitest'; | ||
| import type { ToolHandlerContext, ImageAttachment } from '../rendering/types.ts'; | ||
| import type { PipelineEvent } from '../types/pipeline-events.ts'; | ||
| import type { ToolResponse, NextStepParamsMap } from '../types/common.ts'; | ||
| import type { ToolHandler } from '../utils/typed-tool-factory.ts'; | ||
| import { renderEvents } from '../rendering/render.ts'; | ||
| import { createRenderSession } from '../rendering/render.ts'; | ||
| import { handlerContextStorage } from '../utils/typed-tool-factory.ts'; | ||
|
|
||
| /** | ||
| * Extract and join all text content items from a tool response. | ||
| */ | ||
| export function allText(result: { | ||
| content: ReadonlyArray<{ type: string; text?: string; [key: string]: unknown }>; | ||
| }): string { | ||
| return result.content | ||
| .filter( | ||
| (c): c is { type: 'text'; text: string } => c.type === 'text' && typeof c.text === 'string', | ||
| ) | ||
| .map((c) => c.text) | ||
| .join('\n'); | ||
| } | ||
|
|
||
| /** | ||
| * Assert that a tool response represents a pending xcodebuild result | ||
| * with an optional next-step tool reference. | ||
| */ | ||
| export interface MockToolHandlerResult { | ||
| events: PipelineEvent[]; | ||
| attachments: ImageAttachment[]; | ||
| nextStepParams?: NextStepParamsMap; | ||
| text(): string; | ||
| isError(): boolean; | ||
| } | ||
|
|
||
| export function createMockToolHandlerContext(): { | ||
| ctx: ToolHandlerContext; | ||
| result: MockToolHandlerResult; | ||
| run: <T>(fn: () => Promise<T>) => Promise<T>; | ||
| } { | ||
| const events: PipelineEvent[] = []; | ||
| const attachments: ImageAttachment[] = []; | ||
| const ctx: ToolHandlerContext = { | ||
| emit: (event) => { | ||
| events.push(event); | ||
| }, | ||
| attach: (image) => { | ||
| attachments.push(image); | ||
| }, | ||
| }; | ||
| const resultObj: MockToolHandlerResult = { | ||
| events, | ||
| attachments, | ||
| get nextStepParams() { | ||
| return ctx.nextStepParams; | ||
| }, | ||
| text() { | ||
| return renderEvents(events, 'text'); | ||
| }, | ||
| isError() { | ||
| return events.some( | ||
| (e) => | ||
| (e.type === 'status-line' && e.level === 'error') || | ||
| (e.type === 'summary' && e.status === 'FAILED'), | ||
| ); | ||
| }, | ||
| }; | ||
| return { | ||
| ctx, | ||
| result: resultObj, | ||
| run: async <T>(fn: () => Promise<T>): Promise<T> => { | ||
| return handlerContextStorage.run(ctx, fn); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export async function runToolLogic<T>(logic: () => Promise<T>): Promise<{ | ||
| response: T; | ||
| result: MockToolHandlerResult; | ||
| }> { | ||
| const { result, run } = createMockToolHandlerContext(); | ||
| const response = await run(logic); | ||
| return { response, result }; | ||
| } | ||
|
|
||
| export interface RunLogicResult { | ||
| content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; | ||
| isError?: boolean; | ||
| nextStepParams?: NextStepParamsMap; | ||
| attachments?: ImageAttachment[]; | ||
| text?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Run a tool's logic function in a mock handler context and return a | ||
| * ToolResponse-shaped result for backward-compatible test assertions. | ||
| */ | ||
| export async function runLogic(logic: () => Promise<unknown>): Promise<RunLogicResult> { | ||
| const { result, run } = createMockToolHandlerContext(); | ||
| const response = await run(logic); | ||
|
|
||
| if ( | ||
| response && | ||
| typeof response === 'object' && | ||
| 'content' in (response as Record<string, unknown>) | ||
| ) { | ||
| return response as RunLogicResult; | ||
| } | ||
|
|
||
| const text = result.text(); | ||
| const textContent = text.length > 0 ? [{ type: 'text' as const, text }] : []; | ||
| const imageContent = result.attachments.map((attachment) => ({ | ||
| type: 'image' as const, | ||
| data: attachment.data, | ||
| mimeType: attachment.mimeType, | ||
| })); | ||
|
|
||
| return { | ||
| content: [...textContent, ...imageContent], | ||
| isError: result.isError() ? true : undefined, | ||
| nextStepParams: result.nextStepParams, | ||
| attachments: result.attachments, | ||
| text, | ||
| }; | ||
| } | ||
|
|
||
| export interface CallHandlerResult { | ||
| content: Array<{ type: 'text'; text: string }>; | ||
| isError?: boolean; | ||
| nextStepParams?: NextStepParamsMap; | ||
| } | ||
|
|
||
| /** | ||
| * Call a tool handler in test mode, providing a session context and | ||
| * returning a ToolResponse-shaped result for backward-compatible assertions. | ||
| */ | ||
| export async function callHandler( | ||
| handler: | ||
| | ToolHandler | ||
| | ((args: Record<string, unknown>, ctx?: ToolHandlerContext) => Promise<void>), | ||
| args: Record<string, unknown>, | ||
| ): Promise<CallHandlerResult> { | ||
| const session = createRenderSession('text'); | ||
| const ctx: ToolHandlerContext = { | ||
| emit: (event) => session.emit(event), | ||
| attach: (image) => session.attach(image), | ||
| }; | ||
| await handler(args, ctx); | ||
| const text = session.finalize(); | ||
| return { | ||
| content: text ? [{ type: 'text' as const, text }] : [], | ||
| isError: session.isError() || undefined, | ||
| nextStepParams: ctx.nextStepParams, | ||
| }; | ||
| } | ||
|
|
||
| function isMockToolHandlerResult( | ||
| result: ToolResponse | MockToolHandlerResult, | ||
| ): result is MockToolHandlerResult { | ||
| return 'events' in result && Array.isArray(result.events) && typeof result.text === 'function'; | ||
| } | ||
|
|
||
| export function expectPendingBuildResponse( | ||
| result: ToolResponse | MockToolHandlerResult, | ||
| nextStepToolId?: string, | ||
| ): void { | ||
| if (isMockToolHandlerResult(result)) { | ||
| expect(result.events.some((event) => event.type === 'summary')).toBe(true); | ||
|
|
||
| if (nextStepToolId) { | ||
| expect(result.nextStepParams).toEqual( | ||
| expect.objectContaining({ | ||
| [nextStepToolId]: expect.any(Object), | ||
| }), | ||
| ); | ||
| } else { | ||
| expect(result.nextStepParams).toBeUndefined(); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| expect(result.content).toEqual([]); | ||
| expect(result._meta).toEqual( | ||
| expect.objectContaining({ | ||
| pendingXcodebuild: expect.objectContaining({ | ||
| kind: 'pending-xcodebuild', | ||
| }), | ||
| }), | ||
| ); | ||
|
|
||
| if (nextStepToolId) { | ||
| expect(result.nextStepParams).toEqual( | ||
| expect.objectContaining({ | ||
| [nextStepToolId]: expect.any(Object), | ||
| }), | ||
| ); | ||
| } else { | ||
| expect(result.nextStepParams).toBeUndefined(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a Graphite stack artifact.
handlerContextStoragewill be exported fromtyped-tool-factory.tsin a later PR in this 12-PR stack. CodeQL analyzes each PR in isolation and flags it as undefined, but it will resolve when the full stack merges. Not actionable on this branch.