import type { JSONSchema7 } from "json-schema";
import { describe, expect, it } from "vitest";
import { generateSessionEventsCode as generateCSharpSessionEventsCode } from "../../scripts/codegen/csharp.ts";
import { generateGoSessionEventsCode } from "../../scripts/codegen/go.ts";
import { generatePythonSessionEventsCode } from "../../scripts/codegen/python.ts";
import { generateSessionEventsCode as generateRustSessionEventsCode } from "../../scripts/codegen/rust.ts";
describe("session event codegen", () => {
it("maps special schema formats to the expected Python types", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
required: [
"at",
"identifier",
"duration",
"integerDuration",
"uri",
"pattern",
"payload",
"encoded",
"count",
],
properties: {
at: { type: "string", format: "date-time" },
identifier: { type: "string", format: "uuid" },
duration: { type: "number", format: "duration" },
integerDuration: { type: "integer", format: "duration" },
optionalDuration: {
type: ["number", "null"],
format: "duration",
},
action: {
type: "string",
enum: ["store", "vote"],
default: "store",
},
summary: { type: "string", default: "" },
uri: { type: "string", format: "uri" },
pattern: { type: "string", format: "regex" },
payload: { type: "string", format: "byte" },
encoded: { type: "string", contentEncoding: "base64" },
count: { type: "integer" },
},
},
},
},
],
},
},
};
const code = generatePythonSessionEventsCode(schema);
expect(code).toContain("from datetime import datetime, timedelta");
expect(code).toContain("at: datetime");
expect(code).toContain("identifier: UUID");
expect(code).toContain("duration: timedelta");
expect(code).toContain("integer_duration: timedelta");
expect(code).toContain("optional_duration: timedelta | None = None");
expect(code).toContain('duration = from_timedelta(obj.get("duration"))');
expect(code).toContain('result["duration"] = to_timedelta(self.duration)');
expect(code).toContain(
'result["integerDuration"] = to_timedelta_int(self.integer_duration)'
);
expect(code).toContain("def to_timedelta_int(x: timedelta) -> int:");
expect(code).toContain(
'action = from_union([from_none, lambda x: parse_enum(SessionSyntheticDataAction, x)], obj.get("action"))'
);
expect(code).toContain('summary = from_union([from_none, from_str], obj.get("summary"))');
expect(code).not.toContain('obj.get("action", "store")');
expect(code).not.toContain('obj.get("summary", "")');
expect(code).toContain("uri: str");
expect(code).toContain("pattern: str");
expect(code).toContain("payload: str");
expect(code).toContain("encoded: str");
expect(code).toContain("count: int");
});
it("strips Ms suffixes from duration member names while preserving JSON names", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
required: ["durationMs", "integerDurationMs", "URLMs"],
properties: {
durationMs: { type: "number", format: "duration" },
integerDurationMs: { type: "integer", format: "duration" },
optionalDurationMs: {
type: ["number", "null"],
format: "duration",
},
nullableDurationMs: {
anyOf: [
{ type: "number", format: "duration" },
{ type: "null" },
],
},
URLMs: { type: "number", format: "duration" },
},
},
},
},
],
},
},
};
const pythonCode = generatePythonSessionEventsCode(schema);
expect(pythonCode).toContain("duration: timedelta");
expect(pythonCode).toContain("integer_duration: timedelta");
expect(pythonCode).toContain("optional_duration: timedelta | None = None");
expect(pythonCode).toContain("nullable_duration: timedelta | None = None");
expect(pythonCode).toContain("urlms: timedelta");
expect(pythonCode).toContain('duration = from_timedelta(obj.get("durationMs"))');
expect(pythonCode).toContain('result["durationMs"] = to_timedelta(self.duration)');
expect(pythonCode).toContain(
'integer_duration = from_timedelta(obj.get("integerDurationMs"))'
);
expect(pythonCode).toContain(
'result["integerDurationMs"] = to_timedelta_int(self.integer_duration)'
);
expect(pythonCode).toContain(
'optional_duration = from_union([from_none, from_timedelta], obj.get("optionalDurationMs"))'
);
expect(pythonCode).toContain(
'result["optionalDurationMs"] = from_union([from_none, to_timedelta], self.optional_duration)'
);
expect(pythonCode).toContain(
'nullable_duration = from_union([from_none, from_timedelta], obj.get("nullableDurationMs"))'
);
expect(pythonCode).toContain(
'result["nullableDurationMs"] = from_union([from_none, to_timedelta], self.nullable_duration)'
);
expect(pythonCode).toContain('urlms = from_timedelta(obj.get("URLMs"))');
expect(pythonCode).toContain('result["URLMs"] = to_timedelta(self.urlms)');
const csharpCode = generateCSharpSessionEventsCode(schema);
expect(csharpCode).toContain(
'[JsonPropertyName("durationMs")]\n public required TimeSpan Duration { get; set; }'
);
expect(csharpCode).toContain(
'[JsonPropertyName("integerDurationMs")]\n public required TimeSpan IntegerDuration { get; set; }'
);
expect(csharpCode).toContain(
'[JsonPropertyName("optionalDurationMs")]\n public TimeSpan? OptionalDuration { get; set; }'
);
expect(csharpCode).toContain(
'[JsonPropertyName("nullableDurationMs")]\n public TimeSpan? NullableDuration { get; set; }'
);
expect(csharpCode).toContain(
'[JsonPropertyName("URLMs")]\n public required TimeSpan URLMs { get; set; }'
);
});
it("keeps C# seconds-valued duration members numeric", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
properties: {
retryAfterSeconds: {
type: "integer",
format: "duration",
description:
"Seconds until the rate limit resets, when known.",
},
},
},
},
},
],
},
},
};
const csharpCode = generateCSharpSessionEventsCode(schema);
expect(csharpCode).toContain(
'[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n [JsonPropertyName("retryAfterSeconds")]\n public long? RetryAfterSeconds { get; set; }'
);
expect(csharpCode).not.toContain(
'[JsonConverter(typeof(MillisecondsTimeSpanConverter))]\n [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n [JsonPropertyName("retryAfterSeconds")]'
);
});
it("collapses redundant callable wrapper lambdas", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
properties: {
summary: { type: "string" },
tags: {
type: "array",
items: { type: "string" },
},
context: {
type: "object",
properties: {
gitRoot: { type: "string" },
},
},
},
},
},
},
],
},
},
};
const code = generatePythonSessionEventsCode(schema);
expect(code).toContain('summary = from_union([from_none, from_str], obj.get("summary"))');
expect(code).toContain(
'tags = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tags"))'
);
expect(code).toContain(
'context = from_union([from_none, SessionSyntheticDataContext.from_dict], obj.get("context"))'
);
expect(code).not.toContain("lambda x: from_str(x)");
expect(code).not.toContain("lambda x: SessionSyntheticDataContext.from_dict(x)");
expect(code).not.toContain("from_list(lambda x: from_str(x), x)");
});
it("preserves key shortened nested type names", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "permission.requested" },
data: {
type: "object",
required: ["requestId", "permissionRequest"],
properties: {
requestId: { type: "string" },
permissionRequest: {
anyOf: [
{
type: "object",
required: [
"kind",
"fullCommandText",
"intention",
"commands",
"possiblePaths",
"possibleUrls",
"hasWriteFileRedirection",
"canOfferSessionApproval",
],
properties: {
kind: { const: "shell", type: "string" },
fullCommandText: { type: "string" },
intention: { type: "string" },
commands: {
type: "array",
items: {
type: "object",
required: [
"identifier",
"readOnly",
],
properties: {
identifier: { type: "string" },
readOnly: { type: "boolean" },
},
},
},
possiblePaths: {
type: "array",
items: { type: "string" },
},
possibleUrls: {
type: "array",
items: {
type: "object",
required: ["url"],
properties: {
url: { type: "string" },
},
},
},
hasWriteFileRedirection: {
type: "boolean",
},
canOfferSessionApproval: {
type: "boolean",
},
},
},
{
type: "object",
required: ["kind", "fact"],
properties: {
kind: { const: "memory", type: "string" },
fact: { type: "string" },
action: {
type: "string",
enum: ["store", "vote"],
default: "store",
},
direction: {
type: "string",
enum: ["upvote", "downvote"],
},
},
},
],
},
},
},
},
},
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "elicitation.requested" },
data: {
type: "object",
properties: {
requestedSchema: {
type: "object",
required: ["type", "properties"],
properties: {
type: { const: "object", type: "string" },
properties: {
type: "object",
additionalProperties: {},
},
},
},
mode: {
type: "string",
enum: ["form", "url"],
},
},
},
},
},
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "capabilities.changed" },
data: {
type: "object",
properties: {
ui: {
type: "object",
properties: {
elicitation: { type: "boolean" },
},
},
},
},
},
},
],
},
},
};
const code = generatePythonSessionEventsCode(schema);
expect(code).toContain("class PermissionRequest:");
expect(code).toContain("class PermissionRequestShellCommand:");
expect(code).toContain("class PermissionRequestShellPossibleURL:");
expect(code).toContain("class PermissionRequestMemoryAction(Enum):");
expect(code).toContain("class PermissionRequestMemoryDirection(Enum):");
expect(code).toContain("class ElicitationRequestedSchema:");
expect(code).toContain("class ElicitationRequestedMode(Enum):");
expect(code).toContain("class CapabilitiesChangedUI:");
expect(code).not.toContain("class PermissionRequestedDataPermissionRequest:");
expect(code).not.toContain("class ElicitationRequestedDataRequestedSchema:");
expect(code).not.toContain("class CapabilitiesChangedDataUi:");
});
it("keeps distinct enum types even when they share the same values", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "assistant.message" },
data: {
type: "object",
properties: {
toolRequests: {
type: "array",
items: {
type: "object",
required: ["toolCallId", "name", "type"],
properties: {
toolCallId: { type: "string" },
name: { type: "string" },
type: {
type: "string",
enum: ["function", "custom"],
},
},
},
},
},
},
},
},
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.import_legacy" },
data: {
type: "object",
properties: {
legacySession: {
type: "object",
properties: {
chatMessages: {
type: "array",
items: {
type: "object",
properties: {
toolCalls: {
type: "array",
items: {
type: "object",
properties: {
type: {
type: "string",
enum: [
"function",
"custom",
],
},
},
},
},
},
},
},
},
},
},
},
},
},
],
},
},
};
const code = generatePythonSessionEventsCode(schema);
expect(code).toContain("class AssistantMessageToolRequestType(Enum):");
expect(code).toContain("type: AssistantMessageToolRequestType");
expect(code).toContain("parse_enum(AssistantMessageToolRequestType,");
expect(code).toContain(
"class SessionImportLegacyDataLegacySessionChatMessagesItemToolCallsItemType(Enum):"
);
});
});
describe("enum value description codegen", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
required: ["mode", "fallback"],
properties: {
mode: {
type: "string",
enum: ["alpha", "beta"],
title: "SyntheticMode",
description: "Synthetic mode.",
"x-enumDescriptions": {
alpha: "Use alpha mode.",
},
},
fallback: {
type: "string",
enum: ["plain"],
title: "FallbackMode",
},
},
},
},
},
],
},
},
};
it("emits Python comments for described enum values", () => {
const code = generatePythonSessionEventsCode(schema);
expect(code).toContain("class SyntheticMode(Enum):");
expect(code).toContain(' # Use alpha mode.\n ALPHA = "alpha"');
expect(code).toContain(' BETA = "beta"');
});
it("emits C# XML docs for described enum values and keeps fallback docs", () => {
const code = generateCSharpSessionEventsCode(schema);
expect(code).toContain("public readonly struct SyntheticMode");
expect(code).toContain(
" /// Use alpha mode.\n public static SyntheticMode Alpha"
);
expect(code).toContain(
" /// Gets the plain value.\n public static FallbackMode Plain"
);
});
it("emits Go comments for described enum values", () => {
const code = generateGoSessionEventsCode(schema, "rpc").typeCode;
expect(code).toContain("type SyntheticMode string");
expect(code).toContain(
'\t// Use alpha mode.\n\tSyntheticModeAlpha SyntheticMode = "alpha"'
);
expect(code).toContain('\tSyntheticModeBeta SyntheticMode = "beta"');
});
it("emits Rust docs for described enum values", () => {
const code = generateRustSessionEventsCode(schema);
expect(code).toContain("pub enum SyntheticMode {");
expect(code).toContain(
' /// Use alpha mode.\n #[serde(rename = "alpha")]\n Alpha,'
);
expect(code).toContain(' #[serde(rename = "beta")]\n Beta,');
});
});
describe("csharp session event codegen", () => {
it("emits regular expression attributes for regex format properties with patterns", () => {
const schema: JSONSchema7 = {
definitions: {
SessionEvent: {
anyOf: [
{
type: "object",
required: ["type", "data"],
properties: {
type: { const: "session.synthetic" },
data: {
type: "object",
required: ["pattern"],
properties: {
pattern: {
type: "string",
format: "regex",
pattern: "^foo\\d+$",
},
},
},
},
},
],
},
},
};
const code = generateCSharpSessionEventsCode(schema);
expect(code).toContain(` [StringSyntax(StringSyntaxAttribute.Regex)]
[RegularExpression("^foo\\\\d+$")]
[JsonPropertyName("pattern")]`);
expect(code.split(`[RegularExpression("^foo\\\\d+$")]`)).toHaveLength(2);
});
});