github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/test/web/unit/updater.test.js (about) 1 // Copyright 2021 Daniel Erat. 2 // All rights reserved. 3 4 import { afterEach, beforeEach, expectEq, suite, test } from './test.js'; 5 import MockWindow from './mock-window.js'; 6 import Updater from './updater.js'; 7 8 suite('updater', () => { 9 let w = null; 10 let updater = null; 11 12 const t1 = new Date('2021-04-05T23:12:05.234Z'); 13 const t2 = new Date('2021-04-06T12:05:03Z'); 14 const t3 = new Date('2021-04-07T13:45:12.5Z'); 15 16 beforeEach(() => { 17 w = new MockWindow(); 18 }); 19 afterEach(() => { 20 updater?.destroy(); 21 updater = null; 22 w.finish(); 23 }); 24 25 function initUpdater() { 26 updater?.destroy(); 27 updater = new Updater(); 28 } 29 function playedUrl(songId, startTime) { 30 return ( 31 `played?songId=${encodeURIComponent(songId)}` + 32 `&startTime=${encodeURIComponent(startTime.toISOString())}` 33 ); 34 } 35 function rateAndTagUrl(songId, rating, tags) { 36 let url = `rate_and_tag?songId=${songId}`; 37 if (rating != null) url += `&rating=${rating}`; 38 if (tags != null) url += `&tags=${encodeURIComponent(tags.join(' '))}`; 39 return url; 40 } 41 42 test('reportPlay (success)', async () => { 43 initUpdater(); 44 w.expectFetch(playedUrl('123', t1), 'POST', 'ok'); 45 await updater.reportPlay('123', t1); 46 expectEq(w.numTimeouts, 0, 'numTimeouts'); 47 }); 48 49 test('reportPlay (retry)', async () => { 50 initUpdater(); 51 52 // Report a play and have the server return a 500 error. 53 const id1 = '123'; 54 w.expectFetch(playedUrl(id1, t1), 'POST', 'whoops', 500); 55 await updater.reportPlay(id1, t1); 56 57 // Report a second play and have it also fail. 58 const id2 = '456'; 59 w.expectFetch(playedUrl(id2, t2), 'POST', 'whoops', 500); 60 await updater.reportPlay(id2, t2); 61 62 // 200 ms later, nothing more should have happened. 63 await w.runTimeouts(200); 64 65 // We initially retry at 500 ms, so after 300 ms more, we should try to 66 // report both plays again. 67 w.expectFetch(playedUrl(id1, t1), 'POST', 'ok'); 68 w.expectFetch(playedUrl(id2, t2), 'POST', 'ok'); 69 await w.runTimeouts(300); 70 expectEq(w.numTimeouts, 0, 'numTimeouts'); 71 }); 72 73 test('reportPlay (backoff)', async () => { 74 // Make the initial attempt fail. 75 initUpdater(); 76 w.expectFetch(playedUrl('1', t1), 'POST', 'whoops', 500); 77 await updater.reportPlay('1', t1); 78 79 // The retry time should double up to 5 minutes. 80 for (const ms of [ 81 500, 1_000, 2_000, 4_000, 8_000, 16_000, 32_000, 64_000, 128_000, 256_000, 82 300_000, 300_000, 300_000, 83 ]) { 84 w.expectFetch(playedUrl('1', t1), 'POST', 'fail', 500); 85 await w.runTimeouts(ms); 86 } 87 88 // Try to report a second play and check that it doesn't reset the delay. 89 w.expectFetch(playedUrl('1', t2), 'POST', 'fail', 500); 90 await updater.reportPlay('1', t2); 91 expectEq(w.numUnsatisfiedFetches, 0, 'Unsatisfied fetches'); 92 await w.runTimeouts(299_000); 93 94 // Wait the final second and let the next attempt succeed. 95 w.expectFetch(playedUrl('1', t1), 'POST', 'ok'); 96 w.expectFetch(playedUrl('1', t2), 'POST', 'ok'); 97 await w.runTimeouts(1_000); 98 99 // Report another play and check that it's sent immediately. 100 w.expectFetch(playedUrl('1', t3), 'POST', 'ok'); 101 await updater.reportPlay('1', t3); 102 expectEq(w.numTimeouts, 0, 'numTimeouts'); 103 }); 104 105 test('reportPlay (retry at startup)', async () => { 106 // Make a playback report fail. 107 const id = '1'; 108 initUpdater(); 109 w.expectFetch(playedUrl(id, t1), 'POST', 'fail', 500); 110 await updater.reportPlay(id, t1); 111 112 // Report a second playback, but leave the fetch() hanging. 113 // This should leave the playback in the "active" list in localStorage. 114 w.expectFetchDeferred(playedUrl(id, t2), 'POST', 'fail', 500); 115 updater.reportPlay(id, t2); 116 117 // Clear timeouts to make sure the old updater isn't doing anything and 118 // create a new updater. It should pick up both old reports from 119 // localStorage and try to send the first one again, which again fails (and 120 // gets moved to the end of the queue this time). 121 w.clearTimeouts(); 122 w.expectFetch(playedUrl(id, t1), 'POST', 'fail', 500); 123 initUpdater(); 124 await updater.initialSendDoneForTest; 125 126 // After creating another updater, it should send the second report first 127 // and then the first one. 128 w.clearTimeouts(); 129 w.expectFetch(playedUrl(id, t2), 'POST', 'ok'); 130 initUpdater(); 131 await updater.initialSendDoneForTest; 132 133 w.expectFetch(playedUrl(id, t1), 'POST', 'ok'); 134 await w.runTimeouts(0); 135 expectEq(w.numTimeouts, 0, 'numTimeouts'); 136 137 // If we create a new updater again, nothing should be sent. 138 initUpdater(); 139 await updater.initialSendDoneForTest; 140 expectEq(w.numTimeouts, 0, 'numTimeouts'); 141 }); 142 143 test('reportPlay (overlapping)', async () => { 144 // Report a play, but leave the fetch() call hanging. 145 const id = '1'; 146 initUpdater(); 147 const finishFetch = w.expectFetchDeferred(playedUrl(id, t1), 'POST', 'ok'); 148 const reportDone = updater.reportPlay(id, t1); 149 150 // Successfully report a second play in the meantime. 151 w.expectFetch(playedUrl(id, t2), 'POST', 'ok'); 152 await updater.reportPlay(id, t2); 153 154 // Let the first fetch() finish. 155 finishFetch(); 156 await reportDone; 157 expectEq(w.numTimeouts, 0, 'numTimeouts'); 158 159 // If we create a new updater, nothing should be sent. 160 initUpdater(); 161 await updater.initialSendDoneForTest; 162 expectEq(w.numTimeouts, 0, 'numTimeouts'); 163 }); 164 165 test('reportPlay (online/offline)', async () => { 166 // Make the initial attempt file. 167 const id = '1'; 168 initUpdater(); 169 w.expectFetch(playedUrl(id, t1), 'POST', 'fail', 500); 170 await updater.reportPlay(id, t1); 171 172 // If we're offline when the retry happens, another retry shouldn't be 173 // scheduled. 174 w.online = false; 175 w.expectFetch(playedUrl(id, t1), 'POST', 'fail', 500); 176 await w.runTimeouts(500); 177 expectEq(w.numTimeouts, 0, 'numTimeouts'); 178 179 // As soon as we come back online, an immediate retry should be scheduled. 180 w.expectFetch(playedUrl(id, t1), 'POST'); 181 w.online = true; 182 await w.runTimeouts(0); 183 }); 184 185 test('rateAndTag (success)', async () => { 186 initUpdater(); 187 w.expectFetch(rateAndTagUrl('123', 4, null), 'POST', 'ok'); 188 await updater.rateAndTag('123', 4, null); 189 w.expectFetch(rateAndTagUrl('123', null, ['abc', 'def']), 'POST', 'ok'); 190 await updater.rateAndTag('123', null, ['abc', 'def']); 191 w.expectFetch(rateAndTagUrl('123', 5, ['ijk']), 'POST', 'ok'); 192 await updater.rateAndTag('123', 5, ['ijk']); 193 expectEq(w.numTimeouts, 0, 'numTimeouts'); 194 }); 195 196 test('rateAndTag (retry)', async () => { 197 initUpdater(); 198 199 // Rate and tag a song and have the server report failure. 200 w.expectFetch(rateAndTagUrl('123', 2, ['old']), 'POST', 'bad', 500); 201 await updater.rateAndTag('123', 2, ['old']); 202 203 // Try to send an updated rating and tag for the same song. 204 w.expectFetch(rateAndTagUrl('123', 4, ['new']), 'POST', 'bad', 500); 205 await updater.rateAndTag('123', 4, ['new']); 206 207 // Send a rating and tag for another song. 208 w.expectFetch(rateAndTagUrl('456', 5, ['other']), 'POST', 'bad', 500); 209 await updater.rateAndTag('456', 5, ['other']); 210 211 // After a 500 ms delay, the latest data for each song should be sent. 212 w.expectFetch(rateAndTagUrl('123', 4, ['new']), 'POST', 'ok'); 213 w.expectFetch(rateAndTagUrl('456', 5, ['other']), 'POST', 'ok'); 214 await w.runTimeouts(500); 215 expectEq(w.numTimeouts, 0, 'numTimeouts'); 216 }); 217 218 test('rateAndTag (retry at startup)', async () => { 219 // Make the initial attempt fail. 220 initUpdater(); 221 w.expectFetch(rateAndTagUrl('123', 5, ['tag']), 'POST', 'bad', 500); 222 await updater.rateAndTag('123', 5, ['tag']); 223 224 // Send a second request, but leave the fetch() hanging. 225 // This should leave the update in the "active" list in localStorage. 226 w.expectFetchDeferred(rateAndTagUrl('456', 1, ['a']), 'POST', 'fail', 500); 227 updater.rateAndTag('456', 1, ['a']); 228 229 // Fail again with a new updater. 230 w.clearTimeouts(); 231 w.expectFetch(rateAndTagUrl('123', 5, ['tag']), 'POST', 'bad', 500); 232 initUpdater(); 233 await updater.initialSendDoneForTest; 234 235 // Create another updater and let both updates get sent successfully. 236 w.clearTimeouts(); 237 w.expectFetch(rateAndTagUrl('123', 5, ['tag']), 'POST', 'ok'); 238 w.expectFetch(rateAndTagUrl('456', 1, ['a']), 'POST', 'ok'); 239 initUpdater(); 240 await updater.initialSendDoneForTest; 241 await w.runTimeouts(0); 242 expectEq(w.numTimeouts, 0, 'numTimeouts'); 243 244 // If we create a new updater again, nothing should be sent. 245 initUpdater(); 246 await updater.initialSendDoneForTest; 247 expectEq(w.numTimeouts, 0, 'numTimeouts'); 248 }); 249 });