/**
 * @license
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import '../../../../test/common-test-setup-karma.js';
import {SiteBasedCache, FetchPromisesCache, GrRestApiHelper} from './gr-rest-api-helper.js';
import {appContext} from '../../../../services/app-context.js';

suite('gr-rest-api-helper tests', () => {
  let helper;

  let cache;
  let fetchPromisesCache;
  let originalCanonicalPath;

  setup(() => {
    cache = new SiteBasedCache();
    fetchPromisesCache = new FetchPromisesCache();

    originalCanonicalPath = window.CANONICAL_PATH;
    window.CANONICAL_PATH = 'testhelper';

    const mockRestApiInterface = {
      fire: sinon.stub(),
    };

    const testJSON = ')]}\'\n{"hello": "bonjour"}';
    sinon.stub(window, 'fetch').returns(Promise.resolve({
      ok: true,
      text() {
        return Promise.resolve(testJSON);
      },
    }));

    helper = new GrRestApiHelper(cache, appContext.authService,
        fetchPromisesCache, mockRestApiInterface);
  });

  teardown(() => {
    window.CANONICAL_PATH = originalCanonicalPath;
  });

  suite('fetchJSON()', () => {
    test('Sets header to accept application/json', () => {
      const authFetchStub = sinon.stub(helper._auth, 'fetch')
          .returns(Promise.resolve());
      helper.fetchJSON({url: '/dummy/url'});
      assert.isTrue(authFetchStub.called);
      assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
          'application/json');
    });

    test('Use header option accept when provided', () => {
      const authFetchStub = sinon.stub(helper._auth, 'fetch')
          .returns(Promise.resolve());
      const headers = new Headers();
      headers.append('Accept', '*/*');
      const fetchOptions = {headers};
      helper.fetchJSON({url: '/dummy/url', fetchOptions});
      assert.isTrue(authFetchStub.called);
      assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
          '*/*');
    });
  });

  test('JSON prefix is properly removed',
      () => helper.fetchJSON({url: '/dummy/url'}).then(obj => {
        assert.deepEqual(obj, {hello: 'bonjour'});
      })
  );

  test('cached results', () => {
    let n = 0;
    sinon.stub(helper, 'fetchJSON').callsFake(() => Promise.resolve(++n));
    const promises = [];
    promises.push(helper.fetchCacheURL('/foo'));
    promises.push(helper.fetchCacheURL('/foo'));
    promises.push(helper.fetchCacheURL('/foo'));

    return Promise.all(promises).then(results => {
      assert.deepEqual(results, [1, 1, 1]);
      return helper.fetchCacheURL('/foo').then(foo => {
        assert.equal(foo, 1);
      });
    });
  });

  test('cached promise', () => {
    const promise = Promise.reject(new Error('foo'));
    cache.set('/foo', promise);
    return helper.fetchCacheURL({url: '/foo'}).catch(p => {
      assert.equal(p.message, 'foo');
    });
  });

  test('cache invalidation', () => {
    cache.set('/foo/bar', 1);
    cache.set('/bar', 2);
    fetchPromisesCache.set('/foo/bar', 3);
    fetchPromisesCache.set('/bar', 4);
    helper.invalidateFetchPromisesPrefix('/foo/');
    assert.isFalse(cache.has('/foo/bar'));
    assert.isTrue(cache.has('/bar'));
    assert.isUndefined(fetchPromisesCache.get('/foo/bar'));
    assert.strictEqual(4, fetchPromisesCache.get('/bar'));
  });

  test('params are properly encoded', () => {
    let url = helper.urlWithParams('/path/', {
      sp: 'hola',
      gr: 'guten tag',
      noval: null,
    });
    assert.equal(url,
        window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');

    url = helper.urlWithParams('/path/', {
      sp: 'hola',
      en: ['hey', 'hi'],
    });
    assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');

    // Order must be maintained with array params.
    url = helper.urlWithParams('/path/', {
      l: ['c', 'b', 'a'],
    });
    assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
  });

  test('request callbacks can be canceled', () => {
    let cancelCalled = false;
    window.fetch.returns(Promise.resolve({
      body: {
        cancel() { cancelCalled = true; },
      },
    }));
    const cancelCondition = () => true;
    return helper.fetchJSON({url: '/dummy/url', cancelCondition}).then(obj => {
      assert.isUndefined(obj);
      assert.isTrue(cancelCalled);
    });
  });
});

