remote-method-names.host.spec.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. /**
  2. * A Remote method is reached on the browser as `ctx.remote.<namespace>.<method>`,
  3. * a member of the namespace service the client gateway mounts. The mount
  4. * refuses a method named after one of that service's own members — its
  5. * fields and its private `install`/`remove` helpers — and it refuses at page
  6. * load, after every unit suite passed. This spec reads the reserved names
  7. * off the gateway's own source and checks every `@Remote('<name>')` in the
  8. * workspace against them, so the collision fails here instead.
  9. */
  10. import { readFileSync } from 'node:fs'
  11. import { globSync } from 'node:fs'
  12. import { join } from 'node:path'
  13. import { fileURLToPath } from 'node:url'
  14. import { describe, expect, it } from 'vitest'
  15. const ROOT = fileURLToPath(new URL('../../../..', import.meta.url))
  16. /** The names the client namespace service keeps for itself. */
  17. function reservedMethodNames(): Set<string> {
  18. const source = readFileSync(join(ROOT, 'packages/api/gateway/src/client/index.ts'), 'utf8')
  19. const fields = /const REMOTE_NAMESPACE_FIELDS = new Set\(\[([^\]]*)\]\)/.exec(source)?.[1]
  20. const classBody = /class RemoteNamespaceService extends Service \{([\s\S]*?)\n\}/.exec(source)?.[1]
  21. if (fields === undefined || classBody === undefined) {
  22. throw new Error('the client gateway no longer spells its namespace service the way this spec reads it')
  23. }
  24. const reserved = new Set([...fields.matchAll(/'([^']+)'/g)].map(match => match[1] as string))
  25. for (const match of classBody.matchAll(/^ {2}(?:private |static |readonly |get |async )*([A-Za-z_$][\w$]*)\s*[(:=]/gm)) {
  26. reserved.add(match[1] as string)
  27. }
  28. return reserved
  29. }
  30. describe('Remote method names', () => {
  31. it('never name a member of the client namespace service', () => {
  32. const reserved = reservedMethodNames()
  33. expect(reserved.has('install')).toBe(true)
  34. expect(reserved.has('remove')).toBe(true)
  35. const offenders: string[] = []
  36. for (const file of globSync('packages/*/*/src/**/*.ts', { cwd: ROOT })) {
  37. const source = readFileSync(join(ROOT, file), 'utf8')
  38. for (const match of source.matchAll(/@Remote\(\s*'([^']+)'/g)) {
  39. const method = match[1] as string
  40. if (reserved.has(method)) offenders.push(`${file}: @Remote('${method}')`)
  41. }
  42. }
  43. expect(offenders).toEqual([])
  44. })
  45. })