File size: 3,715 Bytes
26abd99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import http from 'http';
import { AddressInfo } from 'net';
import { assert, expect } from 'chai';
import * as sinon from 'sinon';
import { createHttpServerMock } from '../test/helpers/http-server';
import { LoopbackHandler, LoopbackServer } from './loopback-server';

const DEFAULT_ADDRESS: AddressInfo = {
  address: '127.0.0.1',
  family: 'IPv4',
  port: 12345,
};

describe('LoopbackServer', () => {
  let handler: sinon.SinonStubbedInstance<LoopbackHandler>;
  let server: LoopbackServer;
  let fakeServer: sinon.SinonStubbedInstance<http.Server>;

  beforeEach(() => {
    handler = {
      handleRequest: sinon.stub(),
      handleError: sinon.stub(),
      handleClose: sinon.stub(),
    };
    fakeServer = createHttpServerMock(DEFAULT_ADDRESS);
    sinon.stub(http, 'createServer').returns(fakeServer);

    server = new LoopbackServer(handler);
  });

  afterEach(() => {
    sinon.restore();
    server.dispose();
  });

  describe('dispose', () => {
    it('does nothing when no server is running', () => {
      // Required since `listening` is a readonly getter.
      Object.defineProperty(fakeServer, 'listening', { get: () => false });
      server.dispose();

      sinon.assert.notCalled(fakeServer.close);
    });

    describe('when a server is listening', () => {
      beforeEach(async () => {
        await server.start();
      });

      it('closes the server', () => {
        server.dispose();

        sinon.assert.calledOnce(fakeServer.close);
      });

      it('does nothing if called more than once', () => {
        server.dispose();
        server.dispose();

        sinon.assert.calledOnce(fakeServer.close);
      });
    });
  });

  describe('start', () => {
    it('rejects when a port cannot be obtained', async () => {
      fakeServer.address.returns(null);

      await expect(server.start()).to.be.rejectedWith(
        'Failed to acquire server port',
      );
    });

    it("rejects if a proxied address' port cannot be parsed", async () => {
      fakeServer.address.returns('not-a-port');

      await expect(server.start()).to.be.rejectedWith(
        'Failed to acquire server port',
      );
    });

    it('resolves a port number when started successfully', async () => {
      await expect(server.start()).to.eventually.equal(DEFAULT_ADDRESS.port);
    });

    it('resolves the existing promise if start is called more than once', async () => {
      const firstStart = server.start();
      const secondStart = server.start();

      await expect(firstStart).to.eventually.equal(DEFAULT_ADDRESS.port);
      await expect(secondStart).to.eventually.equal(DEFAULT_ADDRESS.port);

      // Verifies that the underlying server listen was not called a second
      // time.
      sinon.assert.calledOnce(fakeServer.listen);
    });
  });

  describe('server started', () => {
    beforeEach(async () => {
      await server.start();
    });

    it('invokes the handler for incoming requests', () => {
      const req = {} as http.IncomingMessage;
      const res = {} as http.ServerResponse;

      fakeServer.emit('request', req, res);

      sinon.assert.calledOnceWithExactly(handler.handleRequest, req, res);
    });

    it('invokes the handler on error', () => {
      const err = new Error('test error');

      fakeServer.emit('error', err);

      assert.isDefined(handler.handleError);
      sinon.assert.calledOnceWithExactly(handler.handleError, err);
    });

    it('invokes the handler on close', () => {
      fakeServer.emit('close');

      assert.isDefined(handler.handleClose);
      sinon.assert.calledOnce(handler.handleClose);
    });
  });
});