File size: 2,382 Bytes
90f0300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import {
  ApiError,
  rateLimitLockFromError,
  remainingLockSeconds
} from '../client/src/api.js';
import { buildLocalTargetUrl } from '../server/relay-protocol.js';
import {
  nextReconnectDelay,
  shouldResetReconnectDelay
} from './relay-mac-client-reconnect.mjs';
import { fail } from './relay-smoke-env.mjs';

export function runBasicRelayAssertions() {
  expectInvalidForwardPathRejected();
  verifyRateLimitRetryAfterHelpers();
  verifyConnectorReconnectPolicy();
}

function expectInvalidForwardPathRejected() {
  for (const path of ['//example.com/api/status', '/api/status\r\nx: y']) {
    try {
      buildLocalTargetUrl(path, 'http://127.0.0.1:3321');
      fail('invalid forward path should be rejected', path);
    } catch (error) {
      if (error.status !== 400 || error.message !== 'relay_invalid_forward_path') {
        fail('invalid forward path should fail with relay_invalid_forward_path', error);
      }
    }
  }
}

function verifyRateLimitRetryAfterHelpers() {
  const error = new ApiError('请求过快,请稍后再试。', {
    status: 429,
    code: 'relay_rate_limited',
    retryAfter: 3
  });
  const lock = rateLimitLockFromError(error, 'send', 1000);
  if (lock?.scope !== 'send' || lock.retryAfter !== 3 || lock.untilMs !== 4000) {
    fail('rate limit helper should create a scoped retryAfter lock', lock);
  }
  if (remainingLockSeconds(lock, 1001) !== 3 || remainingLockSeconds(lock, 4000) !== 0) {
    fail('rate limit helper should expose remaining lock seconds', lock);
  }
  const ignored = rateLimitLockFromError(new ApiError('offline', { status: 503, code: 'mac_offline' }), 'send', 1000);
  if (ignored) {
    fail('non-rate-limit errors should not create operation locks', ignored);
  }
}

function verifyConnectorReconnectPolicy() {
  let delay = nextReconnectDelay(60000, { active: true, idleHeartbeatMs: 300000 });
  if (delay.delayMs !== 30000 || delay.nextDelayMs !== 30000) {
    fail('active reconnect delay should cap at 30 seconds', delay);
  }
  delay = nextReconnectDelay(600000, { active: false, idleHeartbeatMs: 300000 });
  if (delay.delayMs !== 300000 || delay.nextDelayMs !== 300000) {
    fail('idle reconnect delay should cap at 5 minutes', delay);
  }
  if (shouldResetReconnectDelay(59999) || !shouldResetReconnectDelay(60000)) {
    fail('stable online reconnect delay reset should require 60 seconds');
  }
}