Example #1
0
    it('relies SAML invalidate call even if access token is presented.', async () => {
      const request = requestFixture({ search: '?SAMLRequest=xxx%20yyy' });

      callWithInternalUser.withArgs('shield.samlInvalidate').resolves({ redirect: null });

      const authenticationResult = await provider.deauthenticate(request, {
        accessToken: 'x-saml-token',
        refreshToken: 'x-saml-refresh-token',
      });

      sinon.assert.calledOnce(callWithInternalUser);
      sinon.assert.calledWithExactly(callWithInternalUser, 'shield.samlInvalidate', {
        body: {
          queryString: 'SAMLRequest=xxx%20yyy',
          acs: 'test-protocol://test-hostname:1234/test-base-path/api/security/v1/saml',
        },
      });

      expect(authenticationResult.redirected()).toBe(true);
      expect(authenticationResult.redirectURL).toBe('/logged_out');
    });
Example #2
0
                promizr.reduceRight(list, reduceTotal, iterator).then(result => {
                    sinon.assert.calledThrice(spy);

                    spy.getCall(0).args[0].should.equal(reduceTotal);
                    spy.getCall(0).args[1].should.equal(list[2]);

                    spy.getCall(1).args[0].should.equal(reduceTotal - list[2]);
                    spy.getCall(1).args[1].should.equal(list[1]);

                    spy.getCall(2).args[0].should.equal(reduceTotal - list[2] - list[1]);
                    spy.getCall(2).args[1].should.equal(list[0]);
                }).then(done, done);
Example #3
0
    it('fails if SAML logout call fails.', async () => {
      const request = requestFixture();
      const accessToken = 'x-saml-token';
      const refreshToken = 'x-saml-refresh-token';

      const failureReason = new Error('Realm is misconfigured!');
      callWithInternalUser.withArgs('shield.samlLogout').rejects(failureReason);

      const authenticationResult = await provider.deauthenticate(request, {
        accessToken,
        refreshToken,
      });

      sinon.assert.calledOnce(callWithInternalUser);
      sinon.assert.calledWithExactly(callWithInternalUser, 'shield.samlLogout', {
        body: { token: accessToken, refresh_token: refreshToken },
      });

      expect(authenticationResult.failed()).toBe(true);
      expect(authenticationResult.error).toBe(failureReason);
    });
Example #4
0
    it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => {
      const request = requestFixture({ headers: { authorization: 'Basic some:credentials' } });

      const authenticationResult = await provider.authenticate(request, {
        accessToken: 'some-valid-token',
        refreshToken: 'some-valid-refresh-token',
      });

      sinon.assert.notCalled(callWithRequest);
      expect(request.headers.authorization).toBe('Basic some:credentials');
      expect(authenticationResult.notHandled()).toBe(true);
    });
Example #5
0
    it('succeeds if only `authorization` header is available.', async () => {
      const request = BasicCredentials.decorateRequest(requestFixture(), 'user', 'password');
      const user = { username: 'user' };

      callWithRequest.withArgs(request, 'shield.authenticate').resolves(user);

      const authenticationResult = await provider.authenticate(request);

      expect(authenticationResult.succeeded()).toBe(true);
      expect(authenticationResult.user).toEqual(user);
      sinon.assert.calledOnce(callWithRequest);
    });
Example #6
0
    it('predicates are called with the same context and proper arguments', function () {
        const exampleContext = {some: 'context'},
            structure = {
                key0: sinon.stub().returns(true),
                key1: sinon.stub().returns(true)
            },
            exampleObject = {
                key0: 1,
                key1: 'string'
            };

        const extraArgs = [2, 3];
        const args = [exampleObject, ...extraArgs];
        isValidStructure.call(exampleContext, structure, ...args);
        isValidStructure(structure).call(exampleContext, ...args);

        sinon.assert.calledTwice(structure.key0);
        sinon.assert.calledTwice(structure.key1);

        sinon.assert.alwaysCalledOn(structure.key0, exampleContext);
        sinon.assert.alwaysCalledOn(structure.key1, exampleContext);

        sinon.assert.calledWith(structure.key0, 1, ...extraArgs);
        sinon.assert.calledWith(structure.key1, 'string', ...extraArgs);
    });
Example #7
0
    it('clears session if provider failed to authenticate request with 401 with active session.', async () => {
      const systemAPIRequest = requestFixture({ headers: { xCustomHeader: 'xxx' } });
      const notSystemAPIRequest = requestFixture({ headers: { xCustomHeader: 'yyy' } });

      session.get.withArgs(systemAPIRequest).resolves({
        state: { authorization: 'Basic xxx' },
        provider: 'basic',
      });

      session.get.withArgs(notSystemAPIRequest).resolves({
        state: { authorization: 'Basic yyy' },
        provider: 'basic',
      });

      session.clear.resolves();

      server.plugins.kibana.systemApi.isSystemApiRequest
        .withArgs(systemAPIRequest)
        .returns(true)
        .withArgs(notSystemAPIRequest)
        .returns(false);

      cluster.callWithRequest
        .withArgs(systemAPIRequest)
        .rejects(Boom.unauthorized('token expired'))
        .withArgs(notSystemAPIRequest)
        .rejects(Boom.unauthorized('invalid token'));

      const systemAPIAuthenticationResult = await authenticate(systemAPIRequest);
      expect(systemAPIAuthenticationResult.failed()).toBe(true);

      sinon.assert.calledOnce(session.clear);
      sinon.assert.calledWithExactly(session.clear, systemAPIRequest);

      const notSystemAPIAuthenticationResult = await authenticate(notSystemAPIRequest);
      expect(notSystemAPIAuthenticationResult.failed()).toBe(true);

      sinon.assert.calledTwice(session.clear);
      sinon.assert.calledWithExactly(session.clear, notSystemAPIRequest);
    });
Example #8
0
  it('should not create unnecessary subscriptions with computeds', function() {
    const kObsA = ko.observable("a");
    const kObsB = ko.observable("b");
    const spyA = sinon.spy((a: any) => a);
    const spyB = sinon.spy((a: any) => a);
    const gObsA = computed((use) => spyA(use(kObsA)));
    const gObsB = pureComputed((use) => spyB(use(kObsB)));

    // A computed notices a change immediately.
    assertResetSingleCall(spyA, undefined, "a");
    assert.equal(gObsA.get(), "a");
    kObsA("A");
    assertResetSingleCall(spyA, undefined, "A");
    assert.equal(gObsA.get(), "A");
    sinon.assert.notCalled(spyA);

    // pureComputed notices a change only when looked at.
    sinon.assert.notCalled(spyB);
    assert.equal(gObsB.get(), "b");
    assertResetSingleCall(spyB, undefined, "b");
    kObsB("B");
    sinon.assert.notCalled(spyB);
    assert.equal(gObsB.get(), "B");
    assertResetSingleCall(spyB, undefined, "B");

    // This is the crux of the matter: kObsB does not have a subscription.
    assert.equal(kObsA.getSubscriptionsCount(), 1);
    assert.equal(kObsB.getSubscriptionsCount(), 0);     // pureComputed doesn't subscribe when inactive

    // Now subscribe to both gObs computeds.
    const spyA2 = sinon.spy((a: any) => a);
    const spyB2 = sinon.spy((a: any) => a);
    const lisA = gObsA.addListener(spyA2);
    const lisB = gObsB.addListener(spyB2);
    assertResetSingleCall(spyB, undefined, "B");

    // Now pureComputed acts as computed and subscribes too.
    assert.equal(kObsA.getSubscriptionsCount(), 1);
    assert.equal(kObsB.getSubscriptionsCount(), 1);

    kObsA("aa");
    assertResetSingleCall(spyA, undefined, "aa");
    assertResetSingleCall(spyA2, undefined, "aa", "A");
    kObsB("bb");
    assertResetSingleCall(spyB, undefined, "bb");
    assertResetSingleCall(spyB2, undefined, "bb", "B");

    // When we unsubscribe, count should go back to 0.
    lisA.dispose();
    lisB.dispose();
    assert.equal(kObsA.getSubscriptionsCount(), 1);
    assert.equal(kObsB.getSubscriptionsCount(), 0);

    kObsA("AA");
    assertResetSingleCall(spyA, undefined, "AA");
    sinon.assert.notCalled(spyA2);
    kObsB("bb");
    sinon.assert.notCalled(spyB);
    sinon.assert.notCalled(spyB2);
  });
Example #9
0
  it('should work with nulls', function() {
    let f: Foo|null;
    let g: Foo|null;
    const obs = observable("");
    const comp = computed(obs, (use, val) => val ? Foo.create(use.owner, val) : null);
    f = comp.get();
    assert.strictEqual(f, null);
    sinon.assert.notCalled(fooConstruct);
    sinon.assert.notCalled(fooDispose);

    obs.set("b");     // This should trigger a re-evaluation of comp.
    g = comp.get();
    assertResetSingleCall(fooConstruct, g, "b");
    sinon.assert.notCalled(fooDispose);

    obs.set("");    // Triggers another reevaluation.
    f = comp.get();
    assert.strictEqual(f, null);
    sinon.assert.notCalled(fooConstruct);
    assertResetSingleCall(fooDispose, g);
    assert.isTrue(g && g.isDisposed());

    comp.dispose();
    sinon.assert.notCalled(fooConstruct);
    sinon.assert.notCalled(fooDispose);
  });
Example #10
0
  test('validates various props', () => {
    const validators = {
      a: sinon.stub(),
      b: sinon.stub(),
      c: sinon.stub(),
    };
    docValidator(validators)({ type: 'a', b: 'foo' });

    sinon.assert.notCalled(validators.c);

    expect(validators.a.args).toEqual([[{ type: 'a', b: 'foo' }]]);
    expect(validators.b.args).toEqual([[{ type: 'a', b: 'foo' }]]);
  });
    test('allows for id to be provided', () => {
      const attributes = { foo: 'Foo', bar: 'Bar' };
      const path = `/api/saved_objects/index-pattern/myId`;
      kfetchStub
        .withArgs({
          method: 'POST',
          pathname: path,
          query: undefined,
          body: sinon.match.any,
        })
        .returns(Promise.resolve({}));

      savedObjectsClient.create('index-pattern', attributes, { id: 'myId' });

      sinon.assert.calledOnce(kfetchStub);
      sinon.assert.calledWithExactly(
        kfetchStub,
        sinon.match({
          pathname: path,
        })
      );
    });
Example #12
0
        it("should reject if executor function throw an error", done => {
            const
                err = new Error("This is an error"),
                spy = sinon.spy(),
                promise = new promizr.ProgressPromise(() => { throw err; }).catch(spy);

            sinon.assert.notCalled(spy);

            promise.then(() => {
                sinon.assert.calledOnce(spy);
                sinon.assert.calledWithExactly(spy, err);
            }).then(done, done);
        });
Example #13
0
  test('tasks that return runAt override interval', async () => {
    const runAt = minutesFromNow(_.random(5));
    const { runner, store } = testOpts({
      instance: {
        interval: '20m',
      },
      definitions: {
        bar: {
          createTaskRunner: () => ({
            async run() {
              return { runAt };
            },
          }),
        },
      },
    });

    await runner.run();

    sinon.assert.calledOnce(store.update);
    sinon.assert.calledWithMatch(store.update, { runAt });
  });
Example #14
0
 it('should preserve order and execute task in FIFO style', async () => {
   const sequence = new Sequence(Symbol.for('seq'), seqConfig);
   const spy1     = sinon.stub();
   const spy2     = sinon.stub();
   const spy3     = sinon.stub();
   // This resolves in 5ms
   sequence.addAndPromise(getPromiseWorker('1', false, 5, spy1));
   // This resolves in 3ms
   sequence.addAndPromise(getPromiseWorker('2', false, 3, spy2));
   // This resolves in 1ms
   await sequence.addAndPromise(getPromiseWorker('3', false, 1, spy3));
   sinon.assert.callOrder(spy1, spy2, spy3);
 });
  test('points the alias at the dest index', async () => {
    const opts = defaultOpts();
    const callCluster = clusterStub(opts);

    withIndex(callCluster, { index: { status: 404 }, alias: { status: 404 } });

    await new IndexMigrator(opts).migrate();

    expect(ranMigration(opts)).toBeTruthy();
    sinon.assert.calledWith(callCluster, 'indices.updateAliases', {
      body: { actions: [{ add: { alias: '.kibana', index: '.kibana_1' } }] },
    });
  });
    test('calls indices.create', async () => {
      const callCluster = sinon.spy(async (path: string, { body, index }: any) => {
        expect(path).toEqual('indices.create');
        expect(body).toEqual({
          mappings: { foo: 'bar' },
          settings: { auto_expand_replicas: '0-1', number_of_shards: 1 },
        });
        expect(index).toEqual('.abcd');
      });

      await Index.createIndex(callCluster, '.abcd', { foo: 'bar' } as any);
      sinon.assert.called(callCluster);
    });
Example #17
0
    it("should notice when a Disposable gets disposed outside", function() {
      const holder = Holder.create(null);
      const foo = Foo.create(holder, 16, noop);
      assert.strictEqual(holder.get(), foo);

      foo.dispose();
      assertResetSingleCall(fooDisposed, foo);
      assert.isTrue(holder.isEmpty());
      assert.strictEqual(holder.get(), null);

      holder.clear();
      sinon.assert.notCalled(fooDisposed);
    });
  test('retains mappings from the previous index', async () => {
    const opts = defaultOpts();
    const callCluster = clusterStub(opts);

    opts.mappingProperties = { foo: { type: 'text' } };

    withIndex(callCluster, {
      index: {
        '.kibana_1': {
          aliases: {},
          mappings: {
            properties: {
              author: { type: 'text' },
            },
          },
        },
      },
    });

    await new IndexMigrator(opts).migrate();

    sinon.assert.calledWith(callCluster, 'indices.create', {
      body: {
        mappings: {
          dynamic: 'strict',
          properties: {
            author: { type: 'text' },
            config: {
              dynamic: 'true',
              properties: { buildNum: { type: 'keyword' } },
            },
            foo: { type: 'text' },
            migrationVersion: { dynamic: 'true', type: 'object' },
            namespace: { type: 'keyword' },
            type: { type: 'keyword' },
            updated_at: { type: 'date' },
            references: {
              type: 'nested',
              properties: {
                name: { type: 'keyword' },
                type: { type: 'keyword' },
                id: { type: 'keyword' },
              },
            },
          },
        },
        settings: { number_of_shards: 1, auto_expand_replicas: '0-1' },
      },
      index: '.kibana_2',
    });
  });
Example #19
0
    test('removes the task with the specified id', async () => {
      const id = `id-${_.random(1, 20)}`;
      const callCluster = sinon.spy(() =>
        Promise.resolve({
          _index: 'myindex',
          _id: id,
          _seq_no: 32,
          _primary_term: 32,
          result: 'deleted',
        })
      );
      const store = new TaskStore({
        callCluster,
        getKibanaUuid,
        logger: mockLogger(),
        index: 'myindex',
        maxAttempts: 2,
        supportedTypes: ['a'],
      });
      const result = await store.remove(id);

      sinon.assert.calledOnce(callCluster);
      sinon.assert.calledWith(callCluster, 'delete');

      expect(result).toEqual({
        id,
        index: 'myindex',
        sequenceNumber: 32,
        primaryTerm: 32,
        result: 'deleted',
      });

      expect(callCluster.args[0][1]).toMatchObject({
        id,
        index: 'myindex',
        refresh: true,
      });
    });
Example #20
0
  it('should support pureComputed depending on knockout observables', () => {
    const kObs = ko.observable(17);
    const kComp = ko.computed(() => kObs() * 2);
    const gObs = observable("foo");

    // Check that multiple calls to wrap don't create different observables.
    const gWrap = fromKo(kObs);
    assert.strictEqual(fromKo(kObs), gWrap);

    const stub = sinon.spy((a: any) => a);
    const gComp = pureComputed((use) => stub([use(kObs), use(kComp), use(gObs), use(gWrap)]));

    assert.deepEqual(gComp.get(), [17, 34, "foo", 17]);
    assertResetSingleCall(stub, undefined, [17, 34, "foo", 17]);

    gObs.set("bar");
    sinon.assert.notCalled(stub);
    assert.deepEqual(gComp.get(), [17, 34, "bar", 17]);
    assertResetSingleCall(stub, undefined, [17, 34, "bar", 17]);

    // A change to an observable happens to first trigger kComp, ultimately triggering gComp
    // twice. This is sucky, but is normal for knockout.
    kObs(5);
    sinon.assert.notCalled(stub);
    assert.deepEqual(gComp.get(), [5, 10, "bar", 5]);
    assertResetSingleCall(stub, undefined, [5, 10, "bar", 5]);

    // We can avoid multiple changes at the level of Grain.js observables using bundleChanges().
    bundleChanges(() => kObs(6));
    assert.deepEqual(gComp.get(), [6, 12, "bar", 6]);
    assertResetSingleCall(stub, undefined, [6, 12, "bar", 6]);

    // If we dispose the computed, there are no more calls to it.
    gComp.dispose();
    gObs.set("foo");
    kObs(17);
    sinon.assert.notCalled(stub);
  });
Example #21
0
  it('unload a workspace', async () => {
    const lspservice = mockLspService();
    try {
      const revision = 'master';
      // send a dummy request to open a workspace;
      const response = await sendHoverRequest(lspservice, revision);
      assert.ok(response);
      const workspacePath = path.resolve(serverOptions.workspacePath, repoUri, revision);
      const workspaceFolderExists = fs.existsSync(workspacePath);
      // workspace is opened
      assert.ok(workspaceFolderExists);
      const controller = lspservice.controller;
      // @ts-ignore
      const languageServer = controller.languageServerMap.typescript;
      const realWorkspacePath = fs.realpathSync(workspacePath);

      // @ts-ignore
      const handler = await languageServer.languageServerHandlers[realWorkspacePath];
      const exitSpy = sinon.spy(handler, 'exit');
      const unloadSpy = sinon.spy(handler, 'unloadWorkspace');

      await lspservice.deleteWorkspace(repoUri);

      unloadSpy.restore();
      exitSpy.restore();

      sinon.assert.calledWith(unloadSpy, realWorkspacePath);
      // typescript language server for this workspace should be closed
      sinon.assert.calledOnce(exitSpy);
      // the workspace folder should be deleted
      const exists = fs.existsSync(realWorkspacePath);
      assert.strictEqual(exists, false);
      return;
    } finally {
      await lspservice.shutdown();
    }
    // @ts-ignore
  }).timeout(10000);
Example #22
0
      it('fails if call to delete access token responds with an error', async () => {
        const request = requestFixture();
        const accessToken = 'foo';
        const refreshToken = 'bar';

        const failureReason = new Error('failed to delete token');
        callWithInternalUser
          .withArgs('shield.deleteAccessToken', { body: { token: accessToken } })
          .rejects(failureReason);

        const authenticationResult = await provider.deauthenticate(request, {
          accessToken,
          refreshToken,
        });

        sinon.assert.calledOnce(callWithInternalUser);
        sinon.assert.calledWithExactly(callWithInternalUser, 'shield.deleteAccessToken', {
          body: { token: accessToken },
        });

        expect(authenticationResult.failed()).toBe(true);
        expect(authenticationResult.error).toBe(failureReason);
      });
Example #23
0
    it('returns `notHandled` if state is not presented or does not include access token.', async () => {
      const request = requestFixture();

      let deauthenticateResult = await provider.deauthenticate(request, {});
      expect(deauthenticateResult.notHandled()).toBe(true);

      deauthenticateResult = await provider.deauthenticate(request, {});
      expect(deauthenticateResult.notHandled()).toBe(true);

      deauthenticateResult = await provider.deauthenticate(request, { nonce: 'x' });
      expect(deauthenticateResult.notHandled()).toBe(true);

      sinon.assert.notCalled(callWithInternalUser);
    });
Example #24
0
    it('redirects to OpenID Connect Provider for non-AJAX requests if refresh token is expired or already refreshed.', async () => {
      const request = requestFixture({ path: '/some-path', basePath: '/s/foo' });

      callWithInternalUser.withArgs('shield.oidcPrepare').resolves({
        state: 'statevalue',
        nonce: 'noncevalue',
        redirect:
          'https://op-host/path/login?response_type=code' +
          '&scope=openid%20profile%20email' +
          '&client_id=s6BhdRkqt3' +
          '&state=statevalue' +
          '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc',
      });

      callWithRequest
        .withArgs(
          sinon.match({ headers: { authorization: 'Bearer expired-token' } }),
          'shield.authenticate'
        )
        .rejects({ statusCode: 401 });

      callWithInternalUser
        .withArgs('shield.getAccessToken', {
          body: { grant_type: 'refresh_token', refresh_token: 'expired-refresh-token' },
        })
        .rejects({ statusCode: 400 });

      const authenticationResult = await provider.authenticate(request, {
        accessToken: 'expired-token',
        refreshToken: 'expired-refresh-token',
      });

      sinon.assert.calledWithExactly(callWithInternalUser, 'shield.oidcPrepare', {
        body: { realm: `oidc1` },
      });

      expect(authenticationResult.redirected()).toBe(true);
      expect(authenticationResult.redirectURL).toBe(
        'https://op-host/path/login?response_type=code' +
          '&scope=openid%20profile%20email' +
          '&client_id=s6BhdRkqt3' +
          '&state=statevalue' +
          '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc'
      );
      expect(authenticationResult.state).toEqual({
        state: 'statevalue',
        nonce: 'noncevalue',
        nextURL: `/s/foo/some-path`,
      });
    });
Example #25
0
        it("check main flow", () => {
            const application = new App.Application();
            const app = Express();

            const stubAppUse = Sinon.stub(app, "use");
            application.initRestEndpoints(app);
            stubAppUse.restore();

            Sinon.assert.callOrder(
                stubAppUse.withArgs("/rest/v1/tasks", RouterTasks.router),
                stubAppUse.withArgs("/rest/v1/user", RouterUser.router),
                stubAppUse.withArgs("/rest/v1/report", RouterReport.router),
            );
        });
Example #26
0
  test('removes non-recurring tasks after they complete', async () => {
    const id = _.random(1, 20).toString();
    const { runner, store } = testOpts({
      instance: {
        id,
        interval: undefined,
      },
      definitions: {
        bar: {
          createTaskRunner: () => ({
            async run() {
              return undefined;
            },
          }),
        },
      },
    });

    await runner.run();

    sinon.assert.calledOnce(store.remove);
    sinon.assert.calledWith(store.remove, id);
  });
  test('does not poll if the runMigration succeeds', async () => {
    const log = logStub();
    const pollInterval = 1;
    const runMigration = sinon.spy(() => Promise.resolve());
    const isMigrated = sinon.spy(() => Promise.resolve(true));

    await coordinateMigration({
      log,
      runMigration,
      pollInterval,
      isMigrated,
    });
    sinon.assert.notCalled(isMigrated);
  });
Example #28
0
  test('Creates a single Esqueue worker for Reporting', async () => {
    const createWorker = createWorkerFactory(getMockServer());
    const registerWorkerSpy = sinon.spy(queue, 'registerWorker');

    createWorker(queue);

    sinon.assert.callCount(executeJobFactoryStub, 1);
    sinon.assert.callCount(registerWorkerSpy, 1);

    const { firstCall } = registerWorkerSpy;
    const [workerName, workerFn, workerOpts] = firstCall.args;

    expect(workerName).toBe('reporting');
    expect(workerFn).toMatchInlineSnapshot(`[Function]`);
    expect(workerOpts).toMatchInlineSnapshot(`
Object {
  "interval": 3300,
  "intervalErrorMultiplier": 10,
  "kibanaId": "g9ymiujthvy6v8yrh7567g6fwzgzftzfr",
  "kibanaName": "test-server-123",
}
`);
  });
Example #29
0
    it("should stop reevaluation if element is no more in DOM", (done) => {
        const spy = sinon.spy(() => div.clientWidth);
        simu = simulatedObservable(div, spy);

        sinon.assert.calledOnce(spy);
        sinon.assert.calledWith(spy, div);

        div.style.width = "350px";

        setTimeout(() => {
            sinon.assert.calledTwice(spy);
            sinon.assert.alwaysCalledWith(spy, div);

            div.style.width = "450px";
            div.parentNode.removeChild(div);

            setTimeout(() => {
                sinon.assert.calledTwice(spy);

                done();
            }, 150);
        }, 150);
    });
Example #30
0
    it("should stop reevaluation if dispose function is called", (done) => {
        const spy = sinon.spy(() => div.clientWidth);
        simu = simulatedObservable(div, spy);

        sinon.assert.calledOnce(spy);
        sinon.assert.calledWith(spy, div);

        div.style.width = "350px";

        setTimeout(() => {
            sinon.assert.calledTwice(spy);
            sinon.assert.alwaysCalledWith(spy, div);

            div.style.width = "450px";
            simu.dispose();

            setTimeout(() => {
                sinon.assert.calledTwice(spy);

                done();
            }, 150);
        }, 150);
    });