1- import { beforeEach , describe , expect , it , vi } from 'vitest' ;
1+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
22
33type Result = { data : unknown ; count ?: number ; error ?: { message : string ; code ?: string } | null } ;
44const state = vi . hoisted ( ( ) => ( {
@@ -36,11 +36,16 @@ vi.mock('./supabase', () => ({ supabase: {
3636import { getServerBySlug , listServers , getServersSitemapPage , __resetReadmeLengthProbe } from './queries' ;
3737const outage : Result = { data : null , error : { message : 'timeout' , code : '57014' } } ;
3838const server = { id : 'one' , slug : 'one' , canonical_slug : 'one' , registry_status : 'active' } ;
39+ let errorLog : ReturnType < typeof vi . spyOn > ;
40+ let warnLog : ReturnType < typeof vi . spyOn > ;
3941
4042beforeEach ( ( ) => {
4143 state . results = [ ] ; state . signals = [ ] ; state . reads = 0 ; state . cache . clear ( ) ;
4244 __resetReadmeLengthProbe ( ) ;
45+ errorLog = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => undefined ) ;
46+ warnLog = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => undefined ) ;
4347} ) ;
48+ afterEach ( ( ) => vi . restoreAllMocks ( ) ) ;
4449
4550describe ( 'directory failures do not become persistent content' , ( ) => {
4651 it ( 'fails a canonical lookup promptly without hiding its error behind legacy lookup, then recovers' , async ( ) => {
@@ -58,6 +63,24 @@ describe('directory failures do not become persistent content', () => {
5863 expect ( state . signals ) . toHaveLength ( 3 ) ;
5964 expect ( new Set ( state . signals ) . size ) . toBe ( 1 ) ;
6065 } ) ;
66+ it ( 'skips the tools read only when both authoritative fields prove there are none' , async ( ) => {
67+ const noTools = { ...server , has_tools : false , tool_count : 0 } ;
68+ state . results . push ( { data : noTools } ) ;
69+ expect ( await getServerBySlug ( 'no-tools' ) ) . toMatchObject ( { ...noTools , tools : [ ] } ) ;
70+ expect ( state . reads ) . toBe ( 1 ) ;
71+ expect ( await getServerBySlug ( 'no-tools' ) ) . toMatchObject ( { tools : [ ] } ) ;
72+ expect ( state . reads ) . toBe ( 1 ) ;
73+ } ) ;
74+ it . each ( [
75+ { has_tools : true , tool_count : 0 } ,
76+ { has_tools : false , tool_count : 1 } ,
77+ { has_tools : undefined , tool_count : undefined } ,
78+ ] ) ( 'queries tools when tool state is positive or unknown: %o' , async toolState => {
79+ const slug = `state-${ String ( toolState . has_tools ) } -${ String ( toolState . tool_count ) } ` ;
80+ state . results . push ( { data : { ...server , ...toolState } } , { data : [ { id : 1 } ] } ) ;
81+ expect ( ( await getServerBySlug ( slug ) ) ?. tools ) . toHaveLength ( 1 ) ;
82+ expect ( state . reads ) . toBe ( 2 ) ;
83+ } ) ;
6184 it ( 'only treats two successful absent lookups as missing' , async ( ) => {
6285 state . results . push ( { data : null } , outage ) ;
6386 await expect ( getServerBySlug ( 'missing' ) ) . rejects . toThrow ( ) ;
@@ -67,9 +90,33 @@ describe('directory failures do not become persistent content', () => {
6790 it ( 'does not cache lost tool documentation as an empty tools list' , async ( ) => {
6891 state . results . push ( { data : server } , outage ) ;
6992 await expect ( getServerBySlug ( 'one' ) ) . rejects . toThrow ( ) ;
93+ expect ( errorLog ) . toHaveBeenCalledWith (
94+ '[queries] server detail query failed' ,
95+ expect . objectContaining ( { event : 'server_detail_query_error' , stage : 'tools' } )
96+ ) ;
7097 state . results . push ( { data : server } , { data : [ { name : 'query' } ] } ) ;
7198 expect ( ( await getServerBySlug ( 'one' ) ) ?. tools ) . toHaveLength ( 1 ) ;
7299 } ) ;
100+ it ( 'logs a structured failing stage without upstream messages or identifiers' , async ( ) => {
101+ state . results . push ( { data : null , error : { message : 'secret upstream URL' , code : '57014' } } ) ;
102+ await expect ( getServerBySlug ( 'private-request-slug' ) ) . rejects . toThrow ( 'temporarily unavailable' ) ;
103+ expect ( errorLog ) . toHaveBeenCalledWith (
104+ '[queries] server detail query failed' ,
105+ expect . objectContaining ( {
106+ event : 'server_detail_query_error' , stage : 'canonical' , error_code : '57014' ,
107+ } )
108+ ) ;
109+ expect ( JSON . stringify ( errorLog . mock . calls ) ) . not . toContain ( 'secret upstream URL' ) ;
110+ expect ( JSON . stringify ( errorLog . mock . calls ) ) . not . toContain ( 'private-request-slug' ) ;
111+ } ) ;
112+ it ( 'logs a structured slow stage with duration' , async ( ) => {
113+ vi . spyOn ( performance , 'now' ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 1001 ) ;
114+ state . results . push ( { data : { ...server , has_tools : false , tool_count : 0 } } ) ;
115+ await getServerBySlug ( 'slow' ) ;
116+ expect ( warnLog ) . toHaveBeenCalledWith ( '[queries] server detail query slow' , {
117+ event : 'server_detail_query_slow' , stage : 'canonical' , duration_ms : 1001 ,
118+ } ) ;
119+ } ) ;
73120 it ( 'does not cache listing with a failed count as a successful zero result' , async ( ) => {
74121 state . results . push ( outage , { data : [ server ] } ) ;
75122 await expect ( listServers ( { } ) ) . rejects . toThrow ( ) ;
0 commit comments