|  | 
|  | 1 | +import { CoreModule } from '@module/CoreModule.js'; | 
|  | 2 | +import { container, ContainerImplementation, ProbeModule } from '@powersync/lib-services-framework'; | 
|  | 3 | +import { modules, system, utils } from '@powersync/service-core'; | 
|  | 4 | +import { constants } from 'fs'; | 
|  | 5 | +import fs from 'fs/promises'; | 
|  | 6 | +import path from 'path'; | 
|  | 7 | +import { describe, expect, it } from 'vitest'; | 
|  | 8 | + | 
|  | 9 | +type TestContainerOptions = { | 
|  | 10 | +  yamlConfig: string; | 
|  | 11 | +  mode: system.ServiceContextMode; | 
|  | 12 | +}; | 
|  | 13 | + | 
|  | 14 | +const createTestContainer = async (options: TestContainerOptions) => { | 
|  | 15 | +  // Initialize framework components | 
|  | 16 | +  container.registerDefaults(); | 
|  | 17 | + | 
|  | 18 | +  const moduleManager = new modules.ModuleManager(); | 
|  | 19 | +  moduleManager.register([new CoreModule()]); | 
|  | 20 | + | 
|  | 21 | +  const collectedConfig = await new utils.CompoundConfigCollector().collectConfig({ | 
|  | 22 | +    config_base64: Buffer.from(options.yamlConfig).toString('base64') | 
|  | 23 | +  }); | 
|  | 24 | + | 
|  | 25 | +  const serviceContext = new system.ServiceContextContainer({ | 
|  | 26 | +    configuration: collectedConfig, | 
|  | 27 | +    mode: options.mode | 
|  | 28 | +  }); | 
|  | 29 | + | 
|  | 30 | +  await moduleManager.initialize(serviceContext); | 
|  | 31 | + | 
|  | 32 | +  return { | 
|  | 33 | +    [Symbol.asyncDispose]: async () => { | 
|  | 34 | +      await serviceContext.lifeCycleEngine.stop(); | 
|  | 35 | +    }, | 
|  | 36 | +    serviceContext | 
|  | 37 | +  }; | 
|  | 38 | +}; | 
|  | 39 | + | 
|  | 40 | +describe('Probes', () => { | 
|  | 41 | +  it('should expose HTTP probes in API mode in legacy mode', async () => { | 
|  | 42 | +    await using context = await createTestContainer({ | 
|  | 43 | +      yamlConfig: /* yaml */ ` | 
|  | 44 | +        # Test config | 
|  | 45 | +        telemetry: | 
|  | 46 | +          disable_telemetry_sharing: true | 
|  | 47 | +        storage: | 
|  | 48 | +          type: memory | 
|  | 49 | +      `, | 
|  | 50 | +      mode: system.ServiceContextMode.API | 
|  | 51 | +    }); | 
|  | 52 | + | 
|  | 53 | +    const { serviceContext } = context; | 
|  | 54 | + | 
|  | 55 | +    // The router engine should have been configured with probe routes | 
|  | 56 | +    const testRoute = serviceContext.routerEngine.routes.api_routes.find((r) => r.path == '/probes/startup'); | 
|  | 57 | +    expect(testRoute).exist; | 
|  | 58 | +  }); | 
|  | 59 | + | 
|  | 60 | +  it('should not expose routes in sync mode in legacy mode', async () => { | 
|  | 61 | +    await using context = await createTestContainer({ | 
|  | 62 | +      yamlConfig: /* yaml */ ` | 
|  | 63 | +        # Test config | 
|  | 64 | +        telemetry: | 
|  | 65 | +          disable_telemetry_sharing: true | 
|  | 66 | +        storage: | 
|  | 67 | +          type: memory | 
|  | 68 | +      `, | 
|  | 69 | +      mode: system.ServiceContextMode.SYNC | 
|  | 70 | +    }); | 
|  | 71 | + | 
|  | 72 | +    const { | 
|  | 73 | +      serviceContext: { routerEngine } | 
|  | 74 | +    } = context; | 
|  | 75 | + | 
|  | 76 | +    // The router engine should have been configured with probe routes | 
|  | 77 | +    expect(routerEngine.routes.api_routes).empty; | 
|  | 78 | +    expect(routerEngine.routes.stream_routes).empty; | 
|  | 79 | +    expect(routerEngine.routes.socket_routes).empty; | 
|  | 80 | +  }); | 
|  | 81 | + | 
|  | 82 | +  it('should not expose API routes in sync mode with HTTP probes enabled', async () => { | 
|  | 83 | +    await using context = await createTestContainer({ | 
|  | 84 | +      yamlConfig: /* yaml */ ` | 
|  | 85 | +        # Test config | 
|  | 86 | +        telemetry: | 
|  | 87 | +          disable_telemetry_sharing: true | 
|  | 88 | +        storage: | 
|  | 89 | +          type: memory | 
|  | 90 | +        healthcheck: | 
|  | 91 | +          probes: | 
|  | 92 | +            http: true | 
|  | 93 | +      `, | 
|  | 94 | +      mode: system.ServiceContextMode.SYNC | 
|  | 95 | +    }); | 
|  | 96 | + | 
|  | 97 | +    const { | 
|  | 98 | +      serviceContext: { routerEngine } | 
|  | 99 | +    } = context; | 
|  | 100 | + | 
|  | 101 | +    // The router engine should have been configured with probe routes | 
|  | 102 | +    expect(routerEngine.routes.stream_routes).empty; | 
|  | 103 | +    expect(routerEngine.routes.socket_routes).empty; | 
|  | 104 | +    expect(routerEngine.routes.api_routes.map((r) => r.path)).deep.equal([ | 
|  | 105 | +      '/probes/startup', | 
|  | 106 | +      '/probes/liveness', | 
|  | 107 | +      '/probes/readiness' | 
|  | 108 | +    ]); // Only the HTTP probes | 
|  | 109 | +  }); | 
|  | 110 | + | 
|  | 111 | +  it('should use filesystem probes in legacy mode', async () => { | 
|  | 112 | +    await using context = await createTestContainer({ | 
|  | 113 | +      yamlConfig: /* yaml */ ` | 
|  | 114 | +        # Test config | 
|  | 115 | +        telemetry: | 
|  | 116 | +          disable_telemetry_sharing: true | 
|  | 117 | +        storage: | 
|  | 118 | +          type: memory | 
|  | 119 | +      `, | 
|  | 120 | +      mode: system.ServiceContextMode.API | 
|  | 121 | +    }); | 
|  | 122 | + | 
|  | 123 | +    const { serviceContext } = context; | 
|  | 124 | + | 
|  | 125 | +    // This should be a filesystem probe | 
|  | 126 | +    const probes = serviceContext.get<ProbeModule>(ContainerImplementation.PROBES); | 
|  | 127 | + | 
|  | 128 | +    const aliveProbePath = path.join(process.cwd(), '.probes', 'poll'); | 
|  | 129 | + | 
|  | 130 | +    await fs.unlink(aliveProbePath).catch(() => {}); | 
|  | 131 | + | 
|  | 132 | +    await probes.touch(); | 
|  | 133 | + | 
|  | 134 | +    const exists = await fs | 
|  | 135 | +      .access(aliveProbePath, constants.F_OK) | 
|  | 136 | +      .then(() => true) | 
|  | 137 | +      .catch(() => false); | 
|  | 138 | +    expect(exists).to.be.true; | 
|  | 139 | +  }); | 
|  | 140 | + | 
|  | 141 | +  it('should not use filesystem probes if not enabled', async () => { | 
|  | 142 | +    await using context = await createTestContainer({ | 
|  | 143 | +      yamlConfig: /* yaml */ ` | 
|  | 144 | +        # Test config | 
|  | 145 | +        telemetry: | 
|  | 146 | +          disable_telemetry_sharing: true | 
|  | 147 | +        storage: | 
|  | 148 | +          type: memory | 
|  | 149 | +        healthcheck: | 
|  | 150 | +          probes: | 
|  | 151 | +            http: true | 
|  | 152 | +      `, | 
|  | 153 | +      mode: system.ServiceContextMode.API | 
|  | 154 | +    }); | 
|  | 155 | + | 
|  | 156 | +    const { serviceContext } = context; | 
|  | 157 | + | 
|  | 158 | +    // This should be a filesystem probe | 
|  | 159 | +    const probes = serviceContext.get<ProbeModule>(ContainerImplementation.PROBES); | 
|  | 160 | + | 
|  | 161 | +    const aliveProbePath = path.join(process.cwd(), '.probes', 'poll'); | 
|  | 162 | + | 
|  | 163 | +    await fs.unlink(aliveProbePath).catch(() => {}); | 
|  | 164 | + | 
|  | 165 | +    await probes.touch(); | 
|  | 166 | + | 
|  | 167 | +    const exists = await fs | 
|  | 168 | +      .access(aliveProbePath, constants.F_OK) | 
|  | 169 | +      .then(() => true) | 
|  | 170 | +      .catch(() => false); | 
|  | 171 | +    expect(exists).to.be.false; | 
|  | 172 | +  }); | 
|  | 173 | +}); | 
0 commit comments