GeoIpLookup.test.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { assert, describe, beforeEach, it, vi, expect } from 'vitest'
  2. import path from 'node:path'
  3. import sinon from 'sinon'
  4. const modulePath = path.join(
  5. import.meta.dirname,
  6. '../../../../app/src/infrastructure/GeoIpLookup'
  7. )
  8. describe('GeoIpLookup', function () {
  9. beforeEach(async function (ctx) {
  10. ctx.ipAddress = '12.34.56.78'
  11. ctx.stubbedResponse = {
  12. ip: ctx.ipAddress,
  13. country_code: 'GB',
  14. country_name: 'United Kingdom',
  15. region_code: 'H9',
  16. region_name: 'London, City of',
  17. city: 'London',
  18. zipcode: 'SE16',
  19. latitude: 51.0,
  20. longitude: -0.0493,
  21. metro_code: '',
  22. area_code: '',
  23. }
  24. ctx.fetchUtils = {
  25. fetchJson: sinon.stub().resolves(ctx.stubbedResponse),
  26. }
  27. ctx.settings = {
  28. apis: {
  29. geoIpLookup: {
  30. url: 'http://lookup.com/',
  31. cacheSize: 10_000,
  32. },
  33. },
  34. }
  35. vi.doMock('@overleaf/fetch-utils', () => ({
  36. ...ctx.fetchUtils,
  37. }))
  38. vi.doMock('@overleaf/settings', () => ({
  39. default: ctx.settings,
  40. }))
  41. ctx.GeoIpLookup = (await import(modulePath)).default
  42. })
  43. describe('isValidCurrencyParam', function () {
  44. it('should reject invalid currency codes', function (ctx) {
  45. expect(ctx.GeoIpLookup.isValidCurrencyParam('GBP')).to.equal(true)
  46. expect(ctx.GeoIpLookup.isValidCurrencyParam('USD')).to.equal(true)
  47. expect(ctx.GeoIpLookup.isValidCurrencyParam('AUD')).to.equal(true)
  48. expect(ctx.GeoIpLookup.isValidCurrencyParam('EUR')).to.equal(true)
  49. expect(ctx.GeoIpLookup.isValidCurrencyParam('SGD')).to.equal(true)
  50. expect(ctx.GeoIpLookup.isValidCurrencyParam('WAT')).to.equal(false)
  51. expect(ctx.GeoIpLookup.isValidCurrencyParam('NON')).to.equal(false)
  52. expect(ctx.GeoIpLookup.isValidCurrencyParam('LOL')).to.equal(false)
  53. })
  54. })
  55. describe('getDetails', function () {
  56. beforeEach(function (ctx) {
  57. ctx.fetchUtils.fetchJson.resolves(ctx.stubbedResponse)
  58. })
  59. describe('async', function () {
  60. it('should request the details using the ip', async function (ctx) {
  61. await ctx.GeoIpLookup.promises.getDetails(ctx.ipAddress)
  62. ctx.fetchUtils.fetchJson.should.have.been.calledWith(
  63. new URL(ctx.settings.apis.geoIpLookup.url + ctx.ipAddress)
  64. )
  65. })
  66. it('should return the ip details', async function (ctx) {
  67. const returnedDetails = await ctx.GeoIpLookup.promises.getDetails(
  68. ctx.ipAddress
  69. )
  70. assert.deepEqual(returnedDetails, ctx.stubbedResponse)
  71. })
  72. it('should take the first ip in the string', async function (ctx) {
  73. await ctx.GeoIpLookup.promises.getDetails(
  74. ` ${ctx.ipAddress} 123.123.123.123 234.234.234.234`
  75. )
  76. ctx.fetchUtils.fetchJson.should.have.been.calledWith(
  77. new URL(ctx.settings.apis.geoIpLookup.url + ctx.ipAddress)
  78. )
  79. })
  80. it('should cache lookups by /24 network', async function (ctx) {
  81. const first = await ctx.GeoIpLookup.promises.getDetails('12.34.56.78')
  82. const second = await ctx.GeoIpLookup.promises.getDetails('12.34.56.99')
  83. ctx.fetchUtils.fetchJson.should.have.been.calledOnce
  84. assert.equal(first, second)
  85. assert.deepEqual(second, ctx.stubbedResponse)
  86. })
  87. it('should not share cache entries across different /24 networks', async function (ctx) {
  88. await ctx.GeoIpLookup.promises.getDetails('12.34.56.78')
  89. await ctx.GeoIpLookup.promises.getDetails('12.34.99.1')
  90. ctx.fetchUtils.fetchJson.should.have.been.calledTwice
  91. })
  92. })
  93. })
  94. describe('getCurrencyCode', function () {
  95. describe('async', function () {
  96. it('should return GBP for GB country', async function (ctx) {
  97. const { currencyCode, countryCode } =
  98. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  99. currencyCode.should.equal('GBP')
  100. countryCode.should.equal('GB')
  101. })
  102. it('should return GBP for gb country', async function (ctx) {
  103. ctx.stubbedResponse.country_code = 'gb'
  104. const { currencyCode, countryCode } =
  105. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  106. currencyCode.should.equal('GBP')
  107. countryCode.should.equal('GB')
  108. })
  109. it('should return USD for US', async function (ctx) {
  110. ctx.stubbedResponse.country_code = 'US'
  111. const { currencyCode, countryCode } =
  112. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  113. currencyCode.should.equal('USD')
  114. countryCode.should.equal('US')
  115. })
  116. it('should return EUR for DE', async function (ctx) {
  117. ctx.stubbedResponse.country_code = 'DE'
  118. const { currencyCode, countryCode } =
  119. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  120. currencyCode.should.equal('EUR')
  121. countryCode.should.equal('DE')
  122. })
  123. it('should default to USD if there is an error', async function (ctx) {
  124. ctx.fetchUtils.fetchJson.rejects(new Error('foo'))
  125. const { currencyCode, countryCode } =
  126. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  127. currencyCode.should.equal('USD')
  128. expect(countryCode).to.be.undefined
  129. })
  130. it('should default to USD if there are no details', async function (ctx) {
  131. ctx.fetchUtils.fetchJson.resolves({})
  132. const { currencyCode, countryCode } =
  133. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  134. currencyCode.should.equal('USD')
  135. expect(countryCode).to.be.undefined
  136. })
  137. it('should default to USD if there is no match for their country', async function (ctx) {
  138. ctx.stubbedResponse.country_code = 'Non existant'
  139. const { currencyCode, countryCode } =
  140. await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress)
  141. currencyCode.should.equal('USD')
  142. countryCode.should.equal('NON EXISTANT')
  143. })
  144. })
  145. })
  146. })