File size: 6,076 Bytes
1f5ea39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
137
138
139
import { describe, it, expect, vi } from 'vitest';
import { HttpException } from '@nestjs/common';
import type { Response } from 'express';
import { AtlasController } from '../../../src/nest/atlas/atlas.controller';
import type { AtlasService } from '../../../src/nest/atlas/atlas.service';
import type { User } from '../../../src/types';

const user = { id: 8 } as User;

function makeController(svc: Partial<AtlasService>) {
  return new AtlasController(svc as AtlasService);
}

function makeRes() {
  return { setHeader: vi.fn() } as unknown as Response & { setHeader: ReturnType<typeof vi.fn> };
}

async function thrown(fn: () => unknown): Promise<{ status: number; body: unknown }> {
  try {
    await fn();
  } catch (err) {
    expect(err).toBeInstanceOf(HttpException);
    const e = err as HttpException;
    return { status: e.getStatus(), body: e.getResponse() };
  }
  throw new Error('expected the handler to throw');
}

describe('AtlasController (parity with the legacy /api/addons/atlas route)', () => {
  it('GET /stats delegates with the user id', () => {
    const stats = vi.fn().mockReturnValue({ countries: 3 });
    expect(makeController({ stats }).stats(user)).toEqual({ countries: 3 });
    expect(stats).toHaveBeenCalledWith(8);
  });

  describe('GET /regions/geo', () => {
    it('returns an empty FeatureCollection without a cache header when no countries given', async () => {
      const regionGeo = vi.fn();
      const res = makeRes();
      const out = await makeController({ regionGeo }).regionGeo(undefined, res);
      expect(out).toEqual({ type: 'FeatureCollection', features: [] });
      expect(regionGeo).not.toHaveBeenCalled();
      expect(res.setHeader).not.toHaveBeenCalled();
    });

    it('caches a non-empty result for a day', async () => {
      const regionGeo = vi.fn().mockResolvedValue({ type: 'FeatureCollection', features: [{ id: 1 }] });
      const res = makeRes();
      const out = await makeController({ regionGeo }).regionGeo('DE,FR', res);
      expect(out).toEqual({ type: 'FeatureCollection', features: [{ id: 1 }] });
      expect(regionGeo).toHaveBeenCalledWith(['DE', 'FR']);
      expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=86400');
    });
  });

  it('GET /countries/geo delegates to the service', () => {
    const fc = { type: 'FeatureCollection', features: [{ id: 'NO' }] };
    const countryGeo = vi.fn().mockReturnValue(fc);
    expect(makeController({ countryGeo }).countryGeo()).toBe(fc);
    expect(countryGeo).toHaveBeenCalledWith();
  });

  describe('country', () => {
    it('GET /country/:code upper-cases the code', () => {
      const countryPlaces = vi.fn().mockReturnValue([]);
      makeController({ countryPlaces }).countryPlaces(user, 'de');
      expect(countryPlaces).toHaveBeenCalledWith(8, 'DE');
    });

    it('POST mark returns success and upper-cases', () => {
      const markCountry = vi.fn();
      expect(makeController({ markCountry }).markCountry(user, 'de')).toEqual({ success: true });
      expect(markCountry).toHaveBeenCalledWith(8, 'DE');
    });

    it('DELETE mark returns success', () => {
      const unmarkCountry = vi.fn();
      expect(makeController({ unmarkCountry }).unmarkCountry(user, 'FR')).toEqual({ success: true });
    });
  });

  describe('region', () => {
    it('400 when name or country_code is missing', () => {
      const markRegion = vi.fn();
      return thrown(() => makeController({ markRegion }).markRegion(user, 'by', undefined, 'DE')).then((r) =>
        expect(r).toEqual({ status: 400, body: { error: 'name and country_code are required' } }));
    });

    it('marks a region, upper-casing both codes', () => {
      const markRegion = vi.fn();
      expect(makeController({ markRegion }).markRegion(user, 'by', 'Bavaria', 'de')).toEqual({ success: true });
      expect(markRegion).toHaveBeenCalledWith(8, 'BY', 'Bavaria', 'DE');
    });
  });

  describe('bucket list', () => {
    it('GET wraps the items', () => {
      const bucketList = vi.fn().mockReturnValue([{ id: 1 }]);
      expect(makeController({ bucketList }).bucketList(user)).toEqual({ items: [{ id: 1 }] });
    });

    it('400 on create with a blank name', () => {
      const createBucketItem = vi.fn();
      return thrown(() => makeController({ createBucketItem }).createBucketItem(user, { name: '  ' })).then((r) =>
        expect(r).toEqual({ status: 400, body: { error: 'Name is required' } }));
    });

    it('201-shape create returns { item }', () => {
      const createBucketItem = vi.fn().mockReturnValue({ id: 1, name: 'Tokyo' });
      expect(makeController({ createBucketItem }).createBucketItem(user, { name: 'Tokyo', lat: 35, lng: 139 }))
        .toEqual({ item: { id: 1, name: 'Tokyo' } });
      expect(createBucketItem).toHaveBeenCalledWith(8, { name: 'Tokyo', lat: 35, lng: 139, country_code: undefined, notes: undefined, target_date: undefined });
    });

    it('404 on update of a missing item', () => {
      const updateBucketItem = vi.fn().mockReturnValue(null);
      return thrown(() => makeController({ updateBucketItem }).updateBucketItem(user, '9', { name: 'X' })).then((r) =>
        expect(r).toEqual({ status: 404, body: { error: 'Item not found' } }));
    });

    it('updates an existing item', () => {
      const updateBucketItem = vi.fn().mockReturnValue({ id: 1, name: 'Kyoto' });
      expect(makeController({ updateBucketItem }).updateBucketItem(user, '1', { name: 'Kyoto' }))
        .toEqual({ item: { id: 1, name: 'Kyoto' } });
    });

    it('404 on delete of a missing item', () => {
      const deleteBucketItem = vi.fn().mockReturnValue(false);
      return thrown(() => makeController({ deleteBucketItem }).deleteBucketItem(user, '9')).then((r) =>
        expect(r).toEqual({ status: 404, body: { error: 'Item not found' } }));
    });

    it('deletes an existing item', () => {
      const deleteBucketItem = vi.fn().mockReturnValue(true);
      expect(makeController({ deleteBucketItem }).deleteBucketItem(user, '1')).toEqual({ success: true });
    });
  });
});