File size: 5,358 Bytes
5641073
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
140
141
142
143
144
145
146
147
148
149
const makeServiceWorkerEnv = require('service-worker-mock')

const HASH = '123HASHBROWN'

export const getEvent = (request: Request): any => {
  const waitUntil = async (callback: any) => {
    await callback
  }
  return {
    request,
    waitUntil,
  }
}
const store: any = {
  'key1.123HASHBROWN.txt': 'val1',
  'key1.123HASHBROWN.png': 'val1',
  'index.123HASHBROWN.html': 'index.html',
  'cache.123HASHBROWN.html': 'cache me if you can',
  '测试.123HASHBROWN.html': 'My filename is non-ascii',
  '%not-really-percent-encoded.123HASHBROWN.html': 'browser percent encoded',
  '%2F.123HASHBROWN.html': 'user percent encoded',
  '你好.123HASHBROWN.html': 'I shouldnt be served',
  '%E4%BD%A0%E5%A5%BD.123HASHBROWN.html': 'Im important',
  'nohash.txt': 'no hash but still got some result',
  'sub/blah.123HASHBROWN.png': 'picturedis',
  'sub/index.123HASHBROWN.html': 'picturedis',
  'client.123HASHBROWN': 'important file',
  'client.123HASHBROWN/index.html': 'Im here but serve my big bro above',
  'image.123HASHBROWN.png': 'imagepng',
  'image.123HASHBROWN.webp': 'imagewebp',
  '你好/index.123HASHBROWN.html': 'My path is non-ascii',
}
export const mockKV = (store: any) => {
  return {
    get: (path: string) => store[path] || null,
  }
}

export const mockManifest = () => {
  return JSON.stringify({
    'key1.txt': `key1.${HASH}.txt`,
    'key1.png': `key1.${HASH}.png`,
    'cache.html': `cache.${HASH}.html`,
    '测试.html': `测试.${HASH}.html`,
    '你好.html': `你好.${HASH}.html`,
    '%not-really-percent-encoded.html': `%not-really-percent-encoded.${HASH}.html`,
    '%2F.html': `%2F.${HASH}.html`,
    '%E4%BD%A0%E5%A5%BD.html': `%E4%BD%A0%E5%A5%BD.${HASH}.html`,
    'index.html': `index.${HASH}.html`,
    'sub/blah.png': `sub/blah.${HASH}.png`,
    'sub/index.html': `sub/index.${HASH}.html`,
    client: `client.${HASH}`,
    'client/index.html': `client.${HASH}`,
    'image.png': `image.${HASH}.png`,
    'image.webp': `image.${HASH}.webp`,
    '你好/index.html': `你好/index.${HASH}.html`,
  })
}

let cacheStore: any = new Map()
interface CacheKey {
  url: object
  headers: object
}
export const mockCaches = () => {
  return {
    default: {
      async match(key: any) {
        let cacheKey: CacheKey = {
          url: key.url,
          headers: {},
        }
        let response
        if (key.headers.has('if-none-match')) {
          let makeStrongEtag = key.headers.get('if-none-match').replace('W/', '')
          Reflect.set(cacheKey.headers, 'etag', makeStrongEtag)
          response = cacheStore.get(JSON.stringify(cacheKey))
        } else {
          // if client doesn't send if-none-match, we need to iterate through these keys
          // and just test the URL
          const activeCacheKeys: Array<string> = Array.from(cacheStore.keys())
          for (const cacheStoreKey of activeCacheKeys) {
            if (JSON.parse(cacheStoreKey).url === key.url) {
              response = cacheStore.get(cacheStoreKey)
            }
          }
        }
        // TODO: write test to accomodate for rare scenarios with where range requests accomodate etags
        if (response && !key.headers.has('if-none-match')) {
          // this appears overly verbose, but is necessary to document edge cache behavior
          // The Range request header triggers the response header Content-Range ...
          const range = key.headers.get('range')
          if (range) {
            response.headers.set(
              'content-range',
              `bytes ${range.split('=').pop()}/${response.headers.get('content-length')}`,
            )
          }
          // ... which we are using in this repository to set status 206
          if (response.headers.has('content-range')) {
            response.status = 206
          } else {
            response.status = 200
          }
          let etag = response.headers.get('etag')
          if (etag && !etag.includes('W/')) {
            response.headers.set('etag', `W/${etag}`)
          }
        }
        return response
      },
      async put(key: any, val: Response) {
        let headers = new Headers(val.headers)
        let url = new URL(key.url)
        let resWithBody = new Response(val.body, { headers, status: 200 })
        let resNoBody = new Response(null, { headers, status: 304 })
        let cacheKey: CacheKey = {
          url: key.url,
          headers: {
            etag: `"${url.pathname.replace('/', '')}"`,
          },
        }
        cacheStore.set(JSON.stringify(cacheKey), resNoBody)
        cacheKey.headers = {}
        cacheStore.set(JSON.stringify(cacheKey), resWithBody)
        return
      },
    },
  }
}

// mocks functionality used inside worker request
export function mockRequestScope() {
  Object.assign(global, makeServiceWorkerEnv())
  Object.assign(global, { __STATIC_CONTENT_MANIFEST: mockManifest() })
  Object.assign(global, { __STATIC_CONTENT: mockKV(store) })
  Object.assign(global, { caches: mockCaches() })
}

// mocks functionality used on global isolate scope. such as the KV namespace bind
export function mockGlobalScope() {
  Object.assign(global, { __STATIC_CONTENT_MANIFEST: mockManifest() })
  Object.assign(global, { __STATIC_CONTENT: mockKV(store) })
}

export const sleep = (milliseconds: number) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds))
}