mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-26 06:04:08 +00:00
test(mcp): remove newly added unit test files
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
parent
a954a818c0
commit
d700fc1fad
3 changed files with 0 additions and 409 deletions
|
|
@ -1,107 +0,0 @@
|
|||
import { describe, expect, it } from "vitest"
|
||||
import type { MemoryEntriesResponse, MemoryEntry } from "./client"
|
||||
import { formatMemoryEntriesList } from "./format"
|
||||
|
||||
function memoryEntry(overrides: Partial<MemoryEntry> = {}): MemoryEntry {
|
||||
return {
|
||||
id: "memory-1",
|
||||
memory: "Current fact",
|
||||
version: 1,
|
||||
isLatest: true,
|
||||
isForgotten: false,
|
||||
createdAt: "2026-08-15T10:00:00.000Z",
|
||||
updatedAt: "2026-08-15T10:00:00.000Z",
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function memoryResponse(
|
||||
overrides: Partial<MemoryEntriesResponse> = {},
|
||||
): MemoryEntriesResponse {
|
||||
return {
|
||||
memoryEntries: [],
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
limit: 10,
|
||||
totalItems: 0,
|
||||
totalPages: 1,
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe("formatMemoryEntriesList", () => {
|
||||
it("reports a truly empty store", () => {
|
||||
expect(formatMemoryEntriesList(memoryResponse())).toBe(
|
||||
"No active memories stored yet.",
|
||||
)
|
||||
})
|
||||
|
||||
it("continues past a filtered-empty page when a later page has active data", () => {
|
||||
const firstPage = formatMemoryEntriesList(
|
||||
memoryResponse({
|
||||
memoryEntries: [
|
||||
memoryEntry({ id: "forgotten", isForgotten: true }),
|
||||
memoryEntry({ id: "superseded", isLatest: false }),
|
||||
],
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
limit: 2,
|
||||
totalItems: 3,
|
||||
totalPages: 2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const secondPage = formatMemoryEntriesList(
|
||||
memoryResponse({
|
||||
memoryEntries: [memoryEntry({ id: "still-active" })],
|
||||
pagination: {
|
||||
currentPage: 2,
|
||||
limit: 2,
|
||||
totalItems: 3,
|
||||
totalPages: 2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(firstPage).toBe(
|
||||
"No active memories on page 1 (2 pages total).\n\nMore available - call list_memories with page: 2.",
|
||||
)
|
||||
expect(secondPage).toContain("[still-active] Current fact")
|
||||
})
|
||||
|
||||
it("does not offer another page from a filtered-empty last page", () => {
|
||||
const result = formatMemoryEntriesList(
|
||||
memoryResponse({
|
||||
memoryEntries: [memoryEntry({ isForgotten: true })],
|
||||
pagination: {
|
||||
currentPage: 2,
|
||||
limit: 10,
|
||||
totalItems: 11,
|
||||
totalPages: 2,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result).toBe("No active memories on page 2 (2 pages total).")
|
||||
expect(result).not.toContain("More available")
|
||||
})
|
||||
|
||||
it("keeps active-memory output unchanged", () => {
|
||||
const result = formatMemoryEntriesList(
|
||||
memoryResponse({
|
||||
memoryEntries: [memoryEntry()],
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
limit: 10,
|
||||
totalItems: 1,
|
||||
totalPages: 1,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result).toBe(
|
||||
"1 active memory (page 1 of 1, 1 memory entry total), newest first.\n\n- [memory-1] Current fact\n version 1 | updated 2026-08-15",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import { describe, expect, it, vi } from "vitest"
|
||||
import app from "./index"
|
||||
import { PROTECTED_RESOURCE_METADATA_PATH } from "./oauth-metadata"
|
||||
import type { ServerEnv } from "./types"
|
||||
|
||||
vi.mock("cloudflare:workers", () => ({ DurableObject: class {} }))
|
||||
vi.mock("../../dist/src/widget/index.html", () => ({ default: "" }))
|
||||
|
||||
describe("OAuth protected-resource metadata challenge", () => {
|
||||
it("ignores spoofed forwarding headers", async () => {
|
||||
const resource = "https://mcp.example.com/mcp"
|
||||
const response = await app.request(
|
||||
"https://mcp.example.com/mcp",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "attacker.example",
|
||||
"x-forwarded-host": "attacker.example",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
},
|
||||
{ MCP_RESOURCE: resource } as ServerEnv,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.headers.get("WWW-Authenticate")).toBe(
|
||||
`Bearer resource_metadata="https://mcp.example.com${PROTECTED_RESOURCE_METADATA_PATH}/mcp"`,
|
||||
)
|
||||
})
|
||||
|
||||
it("keeps discovery aligned behind a trusted proxy", async () => {
|
||||
const resource = "https://memory.example.com/gateway/mcp/?tenant=acme"
|
||||
const metadataUrl =
|
||||
"https://memory.example.com/.well-known/oauth-protected-resource/gateway/mcp/?tenant=acme"
|
||||
const env = { MCP_RESOURCE: resource } as ServerEnv
|
||||
const response = await app.request(
|
||||
"http://worker.internal/mcp",
|
||||
{ method: "POST" },
|
||||
env,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.headers.get("WWW-Authenticate")).toBe(
|
||||
`Bearer resource_metadata="${metadataUrl}"`,
|
||||
)
|
||||
|
||||
const metadataResponse = await app.request(metadataUrl, undefined, env)
|
||||
expect(metadataResponse.status).toBe(200)
|
||||
await expect(metadataResponse.json()).resolves.toMatchObject({ resource })
|
||||
|
||||
const rootFallbackResponse = await app.request(
|
||||
`https://memory.example.com${PROTECTED_RESOURCE_METADATA_PATH}`,
|
||||
undefined,
|
||||
env,
|
||||
)
|
||||
expect(rootFallbackResponse.status).toBe(200)
|
||||
|
||||
for (const mismatchedUrl of [
|
||||
metadataUrl.replace("tenant=acme", "tenant=other"),
|
||||
metadataUrl.replace("/gateway/mcp/", "/other/"),
|
||||
`https://memory.example.com${PROTECTED_RESOURCE_METADATA_PATH}?tenant=acme`,
|
||||
]) {
|
||||
const mismatchedResponse = await app.request(
|
||||
mismatchedUrl,
|
||||
undefined,
|
||||
env,
|
||||
)
|
||||
expect(mismatchedResponse.status).toBe(404)
|
||||
}
|
||||
})
|
||||
|
||||
it("supports a resource identifier without a path", async () => {
|
||||
const resource = "https://mcp.example.com"
|
||||
const response = await app.request(resource, { method: "POST" }, {
|
||||
MCP_RESOURCE: resource,
|
||||
} as ServerEnv)
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.headers.get("WWW-Authenticate")).toBe(
|
||||
`Bearer resource_metadata="https://mcp.example.com${PROTECTED_RESOURCE_METADATA_PATH}"`,
|
||||
)
|
||||
|
||||
const metadataResponse = await app.request(
|
||||
`https://mcp.example.com${PROTECTED_RESOURCE_METADATA_PATH}`,
|
||||
undefined,
|
||||
{ MCP_RESOURCE: resource } as ServerEnv,
|
||||
)
|
||||
expect(metadataResponse.status).toBe(200)
|
||||
await expect(metadataResponse.json()).resolves.toMatchObject({ resource })
|
||||
})
|
||||
})
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
import {
|
||||
CLIENT_CAPABILITIES_META_KEY,
|
||||
isInputRequiredResult,
|
||||
} from "@modelcontextprotocol/server"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import type { z } from "zod"
|
||||
import * as addMemory from "./add-memory"
|
||||
import * as forgetMemory from "./forget-memory"
|
||||
import type { ToolDeps } from "./types"
|
||||
import { errorResult } from "./types"
|
||||
|
||||
type Registered = {
|
||||
config: { outputSchema: z.ZodTypeAny }
|
||||
cb: (
|
||||
args: Record<string, unknown>,
|
||||
context: unknown,
|
||||
) => Promise<Record<string, unknown>>
|
||||
}
|
||||
|
||||
const candidate = { id: "m1", memory: "User lives in Pune" }
|
||||
|
||||
function fakeDeps() {
|
||||
const tools = new Map<string, Registered>()
|
||||
const client = {
|
||||
findMemoryToForget: vi.fn().mockResolvedValue(candidate),
|
||||
getMemory: vi.fn(async (id: string) => (id === "m1" ? candidate : null)),
|
||||
forgetMemoryById: vi.fn().mockResolvedValue({
|
||||
success: true,
|
||||
message: 'Forgot memory (ID: m1): "User lives in Pune"',
|
||||
namespace: "active",
|
||||
}),
|
||||
}
|
||||
const deps = {
|
||||
server: {
|
||||
registerTool: (name: string, config: unknown, cb: unknown) => {
|
||||
tools.set(name, { config, cb } as Registered)
|
||||
},
|
||||
},
|
||||
getClient: vi.fn(() => client),
|
||||
resolveNamespace: vi.fn(async (explicit?: string) => explicit ?? "active"),
|
||||
errorResult,
|
||||
} as unknown as ToolDeps
|
||||
forgetMemory.register(deps)
|
||||
addMemory.register(deps)
|
||||
const tool = (name: string) => tools.get(name) as Registered
|
||||
return { tool, client }
|
||||
}
|
||||
|
||||
// A 2026-era client that declared elicitation, optionally answering a previous prompt.
|
||||
function elicitingContext(
|
||||
answer?: Record<string, unknown>,
|
||||
requestState?: string,
|
||||
) {
|
||||
return {
|
||||
mcpReq: {
|
||||
envelope: { [CLIENT_CAPABILITIES_META_KEY]: { elicitation: {} } },
|
||||
inputResponses: answer ? { confirm_forget: answer } : undefined,
|
||||
requestState: () => requestState,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("forget_memory without elicitation", () => {
|
||||
it("only previews the match on the first call", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
const forget = tool("forget_memory")
|
||||
|
||||
const result = await forget.cb({ content: "where I live" }, {})
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
const output = forget.config.outputSchema.parse(result.structuredContent)
|
||||
expect(output).toMatchObject({
|
||||
success: false,
|
||||
status: "confirmation_required",
|
||||
id: "m1",
|
||||
memory: "User lives in Pune",
|
||||
})
|
||||
})
|
||||
|
||||
it("forgets once called again with the previewed id and confirm", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
const forget = tool("forget_memory")
|
||||
|
||||
const result = await forget.cb({ id: "m1", confirm: true }, {})
|
||||
|
||||
expect(client.forgetMemoryById).toHaveBeenCalledWith(candidate)
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
success: true,
|
||||
status: "forgotten",
|
||||
})
|
||||
})
|
||||
|
||||
it("does not treat confirm on a content match as consent", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ content: "where I live", confirm: true },
|
||||
{},
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
status: "confirmation_required",
|
||||
})
|
||||
})
|
||||
|
||||
it("reports a missing memory without forgetting anything", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ id: "gone", confirm: true },
|
||||
{},
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(result.structuredContent).toMatchObject({ status: "not_found" })
|
||||
})
|
||||
|
||||
it("rejects calls with neither id nor content", async () => {
|
||||
const { tool } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb({}, {})
|
||||
|
||||
expect(result.isError).toBe(true)
|
||||
})
|
||||
|
||||
it("routes the legacy add_memory forget action through confirmation", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("add_memory").cb(
|
||||
{ content: "where I live", action: "forget" },
|
||||
{},
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
status: "confirmation_required",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("forget_memory with elicitation", () => {
|
||||
it("asks the user before forgetting", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ content: "where I live" },
|
||||
elicitingContext(),
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(isInputRequiredResult(result)).toBe(true)
|
||||
expect(result).toMatchObject({ requestState: "m1" })
|
||||
expect(JSON.stringify(result)).toContain("User lives in Pune")
|
||||
})
|
||||
|
||||
it("ignores a model-supplied confirm and still asks the user", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ id: "m1", confirm: true },
|
||||
elicitingContext(),
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(isInputRequiredResult(result)).toBe(true)
|
||||
})
|
||||
|
||||
it("forgets after the user accepts", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ content: "where I live" },
|
||||
elicitingContext({ action: "accept", content: { confirm: true } }, "m1"),
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).toHaveBeenCalledWith(candidate)
|
||||
expect(result.structuredContent).toMatchObject({ status: "forgotten" })
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ action: "decline" },
|
||||
{ action: "cancel" },
|
||||
{ action: "accept", content: { confirm: false } },
|
||||
])("keeps the memory when the user answers %o", async (answer) => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ content: "where I live" },
|
||||
elicitingContext(answer, "m1"),
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
success: false,
|
||||
status: "cancelled",
|
||||
})
|
||||
})
|
||||
|
||||
it("asks again when the match changed since the user was asked", async () => {
|
||||
const { tool, client } = fakeDeps()
|
||||
|
||||
const result = await tool("forget_memory").cb(
|
||||
{ content: "where I live" },
|
||||
elicitingContext({ action: "accept", content: { confirm: true } }, "m0"),
|
||||
)
|
||||
|
||||
expect(client.forgetMemoryById).not.toHaveBeenCalled()
|
||||
expect(isInputRequiredResult(result)).toBe(true)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue