summaryrefslogtreecommitdiffstats
path: root/services/common/tests/unit/test_blocklist_updater.js
blob: 1b71c194a040ab013577ac6475b4cd7a3719effd (plain)
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
Cu.import("resource://testing-common/httpd.js");

var server;

const PREF_SETTINGS_SERVER = "services.settings.server";
const PREF_LAST_UPDATE = "services.blocklist.last_update_seconds";
const PREF_LAST_ETAG = "services.blocklist.last_etag";
const PREF_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";

// Check to ensure maybeSync is called with correct values when a changes
// document contains information on when a collection was last modified
add_task(function* test_check_maybeSync(){
  const changesPath = "/v1/buckets/monitor/collections/changes/records";

  // register a handler
  function handleResponse (serverTimeMillis, request, response) {
    try {
      const sampled = getSampleResponse(request, server.identity.primaryPort);
      if (!sampled) {
        do_throw(`unexpected ${request.method} request for ${request.path}?${request.queryString}`);
      }

      response.setStatusLine(null, sampled.status.status,
                             sampled.status.statusText);
      // send the headers
      for (let headerLine of sampled.sampleHeaders) {
        let headerElements = headerLine.split(':');
        response.setHeader(headerElements[0], headerElements[1].trimLeft());
      }

      // set the server date
      response.setHeader("Date", (new Date(serverTimeMillis)).toUTCString());

      response.write(sampled.responseBody);
    } catch (e) {
      dump(`${e}\n`);
    }
  }

  server.registerPathHandler(changesPath, handleResponse.bind(null, 2000));

  // set up prefs so the kinto updater talks to the test server
  Services.prefs.setCharPref(PREF_SETTINGS_SERVER,
    `http://localhost:${server.identity.primaryPort}/v1`);

  // set some initial values so we can check these are updated appropriately
  Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
  Services.prefs.setIntPref(PREF_CLOCK_SKEW_SECONDS, 0);
  Services.prefs.clearUserPref(PREF_LAST_ETAG);


  let startTime = Date.now();

  let updater = Cu.import("resource://services-common/blocklist-updater.js");

  let syncPromise = new Promise(function(resolve, reject) {
    // add a test kinto client that will respond to lastModified information
    // for a collection called 'test-collection'
    updater.addTestBlocklistClient("test-collection", {
      maybeSync(lastModified, serverTime) {
        do_check_eq(lastModified, 1000);
        do_check_eq(serverTime, 2000);
        resolve();
      }
    });
    updater.checkVersions();
  });

  // ensure we get the maybeSync call
  yield syncPromise;

  // check the last_update is updated
  do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);

  // How does the clock difference look?
  let endTime = Date.now();
  let clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS);
  // we previously set the serverTime to 2 (seconds past epoch)
  do_check_true(clockDifference <= endTime / 1000
              && clockDifference >= Math.floor(startTime / 1000) - 2);
  // Last timestamp was saved. An ETag header value is a quoted string.
  let lastEtag = Services.prefs.getCharPref(PREF_LAST_ETAG);
  do_check_eq(lastEtag, "\"1100\"");

  // Simulate a poll with up-to-date collection.
  Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
  // If server has no change, a 304 is received, maybeSync() is not called.
  updater.addTestBlocklistClient("test-collection", {
    maybeSync: () => {throw new Error("Should not be called");}
  });
  yield updater.checkVersions();
  // Last update is overwritten
  do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);


  // Simulate a server error.
  function simulateErrorResponse (request, response) {
    response.setHeader("Date", (new Date(3000)).toUTCString());
    response.setHeader("Content-Type", "application/json; charset=UTF-8");
    response.write(JSON.stringify({
      code: 503,
      errno: 999,
      error: "Service Unavailable",
    }));
    response.setStatusLine(null, 503, "Service Unavailable");
  }
  server.registerPathHandler(changesPath, simulateErrorResponse);
  // checkVersions() fails with adequate error.
  let error;
  try {
    yield updater.checkVersions();
  } catch (e) {
    error = e;
  }
  do_check_eq(error.message, "Polling for changes failed.");
  // When an error occurs, last update was not overwritten (see Date header above).
  do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);

  // check negative clock skew times

  // set to a time in the future
  server.registerPathHandler(changesPath, handleResponse.bind(null, Date.now() + 10000));

  yield updater.checkVersions();

  clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS);
  // we previously set the serverTime to Date.now() + 10000 ms past epoch
  do_check_true(clockDifference <= 0 && clockDifference >= -10);
});

function run_test() {
  // Set up an HTTP Server
  server = new HttpServer();
  server.start(-1);

  run_next_test();

  do_register_cleanup(function() {
    server.stop(function() { });
  });
}

// get a response for a given request from sample data
function getSampleResponse(req, port) {
  const responses = {
    "GET:/v1/buckets/monitor/collections/changes/records?": {
      "sampleHeaders": [
        "Content-Type: application/json; charset=UTF-8",
        "ETag: \"1100\""
      ],
      "status": {status: 200, statusText: "OK"},
      "responseBody": JSON.stringify({"data": [{
        "host": "localhost",
        "last_modified": 1100,
        "bucket": "blocklists:aurora",
        "id": "330a0c5f-fadf-ff0b-40c8-4eb0d924ff6a",
        "collection": "test-collection"
      }, {
        "host": "localhost",
        "last_modified": 1000,
        "bucket": "blocklists",
        "id": "254cbb9e-6888-4d9f-8e60-58b74faa8778",
        "collection": "test-collection"
      }]})
    }
  };

  if (req.hasHeader("if-none-match") && req.getHeader("if-none-match", "") == "\"1100\"")
    return {sampleHeaders: [], status: {status: 304, statusText: "Not Modified"}, responseBody: ""};

  return responses[`${req.method}:${req.path}?${req.queryString}`] ||
         responses[req.method];
}