1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Stub out the `chrome.proxy`, `chrome.i18n`, and `chrome.extension` APIs
6chrome = chrome || {
7  proxy: {
8    settings: {
9      get: function() {},
10      clear: function() {},
11      set: function() {}
12    }
13  },
14  i18n: {
15    getMessage: function(x) { return x; }
16  },
17  extension: {
18    sendRequest: function() {},
19    isAllowedIncognitoAccess: function(funk) {
20      funk(true);
21    }
22  }
23};
24
25var fixture = document.getElementById('fixture');
26var baselineHTML = fixture.innerHTML;
27var groupIDs = [ProxyFormController.ProxyTypes.DIRECT,
28                ProxyFormController.ProxyTypes.SYSTEM,
29                ProxyFormController.ProxyTypes.PAC,
30                ProxyFormController.ProxyTypes.FIXED];
31
32var mockFunctionFactory = function(returnValue, logging) {
33  var called = [];
34  returnValue = returnValue || null;
35
36  var funky = function() {
37    called.push(arguments);
38    if (arguments[1] && typeof(arguments[1]) === 'function') {
39      var funk = arguments[1];
40      funk(returnValue);
41    }
42    return returnValue;
43  };
44  funky.getCallList = function() { return called; };
45  funky.getValue = function() { return returnValue; };
46  return funky;
47};
48
49var proxyform = new Test.Unit.Runner({
50  setup: function() {
51    fixture.innerHTML = baselineHTML;
52    this.controller_ = new ProxyFormController('proxyForm');
53    this.clickEvent_ = document.createEvent('MouseEvents');
54    this.clickEvent_.initMouseEvent('click', true, true, window,
55        0, 0, 0, 0, 0, false, false, false, false, 0, null);
56    // Reset mock functions.
57    chrome.proxy = {
58      settings: {
59        get: mockFunctionFactory({
60               value: {mode: 'system' },
61               levelOfControl: 'controllable_by_this_extension' }),
62        clear: mockFunctionFactory({
63                 value: {mode: 'system' },
64                 levelOfControl: 'controllable_by_this_extension' }),
65        set: mockFunctionFactory({
66               value: {mode: 'system' },
67               levelOfControl: 'controllable_by_this_extension' })
68      }
69    };
70  },
71
72  teardown: function() {
73    fixture.removeChild(fixture.childNodes[0]);
74    delete(this.controller_);
75  },
76
77  // Clicking on various bits of the interface should set correct classes,
78  // and select correct radio buttons.
79  testActivationClicks: function() {
80    var self = this;
81    var i;
82    groupIDs.forEach(function(id) {
83      var group = document.getElementById(id);
84      var all = group.querySelectorAll('*');
85      for (i = 0; i < all.length; i++) {
86        group.classList.remove('active');
87        all[i].dispatchEvent(self.clickEvent_);
88        self.assert(group.classList.contains('active'));
89      }
90    });
91  },
92
93  // Elements inside an active group should not be disabled, and vice versa
94  testDisabledElements: function() {
95    var self = this;
96    var i, j;
97    groupIDs.forEach(function(id) {
98      var group = document.getElementById(id);
99      var all = group.querySelectorAll('*');
100      // First, check that activating a group enables its form elements
101      for (i = 0; i < all.length; i++) {
102        group.classList.remove('active');
103        var inputs = group.querySelectorAll('input:not([type="radio"]),select');
104        for (j = 0; j < inputs.length; j++) {
105          inputs[j].setAttribute('disabled', 'disabled');
106        }
107        all[i].dispatchEvent(self.clickEvent_);
108        for (j = 0; j < inputs.length; j++) {
109          self.assert(!inputs[j].hasAttribute('disabled'));
110        }
111      }
112    });
113  },
114
115  // Clicking the "Use single proxy" checkbox should set the correct
116  // classes on the form.
117  testSingleProxyToggle: function() {
118    var group = document.getElementById(
119        ProxyFormController.ProxyTypes.FIXED);
120    var checkbox = document.getElementById('singleProxyForEverything');
121    var section = checkbox.parentNode.parentNode;
122    // Checkbox only works in active group, `testActivationClicks` tests
123    // the inactive click behavior.
124    group.classList.add('active');
125
126    checkbox.checked = false;
127    checkbox.dispatchEvent(this.clickEvent_);
128    this.assert(section.classList.contains('single'));
129    checkbox.dispatchEvent(this.clickEvent_);
130    this.assert(!section.classList.contains('single'));
131  },
132
133  // On instantiation, ProxyFormController should read the current state
134  // from `chrome.proxy.settings.get`, and react accordingly.
135  // Let's see if that happens with the next four sets of assertions.
136  testSetupFormSystem: function() {
137    chrome.proxy.settings.get = mockFunctionFactory({
138      value: {mode: 'system'},
139      levelOfControl: 'controllable_by_this_extension'
140    });
141
142    fixture.innerHTML = baselineHTML;
143    this.controller_ = new ProxyFormController('proxyForm');
144    // Wait for async calls to fire
145    this.wait(100, function() {
146      this.assertEqual(
147          6,
148          chrome.proxy.settings.get.getCallList().length);
149      this.assert(
150          document.getElementById(ProxyFormController.ProxyTypes.SYSTEM)
151              .classList.contains('active'));
152    });
153  },
154
155  testSetupFormDirect: function() {
156    chrome.proxy.settings.get =
157        mockFunctionFactory({value: {mode: 'direct'},
158             levelOfControl: 'controllable_by_this_extension'}, true);
159
160    fixture.innerHTML = baselineHTML;
161    this.controller_ = new ProxyFormController('proxyForm');
162    // Wait for async calls to fire
163    this.wait(100, function() {
164      this.assertEqual(
165          2,
166          chrome.proxy.settings.get.getCallList().length);
167      this.assert(
168          document.getElementById(ProxyFormController.ProxyTypes.DIRECT)
169              .classList.contains('active'));
170    });
171  },
172
173  testSetupFormPac: function() {
174    chrome.proxy.settings.get =
175        mockFunctionFactory({value: {mode: 'pac_script' },
176             levelOfControl: 'controllable_by_this_extension'});
177
178    fixture.innerHTML = baselineHTML;
179    this.controller_ = new ProxyFormController('proxyForm');
180    // Wait for async calls to fire
181    this.wait(100, function() {
182      this.assertEqual(
183          2,
184          chrome.proxy.settings.get.getCallList().length);
185      this.assert(
186          document.getElementById(ProxyFormController.ProxyTypes.PAC)
187              .classList.contains('active'));
188    });
189  },
190
191  testSetupFormFixed: function() {
192    chrome.proxy.settings.get =
193        mockFunctionFactory({value: {mode: 'fixed_servers' },
194             levelOfControl: 'controllable_by_this_extension'});
195
196    fixture.innerHTML = baselineHTML;
197    this.controller_ = new ProxyFormController('proxyForm');
198    // Wait for async calls to fire
199    this.wait(100, function() {
200      this.assertEqual(
201          2,
202          chrome.proxy.settings.get.getCallList().length);
203      this.assert(
204          document.getElementById(ProxyFormController.ProxyTypes.FIXED)
205              .classList.contains('active'));
206    });
207  },
208
209  // Test that `recalcFormValues_` correctly sets DOM field values when
210  // given a `ProxyConfig` structure
211  testRecalcFormValuesGroups: function() {
212    // Test `AUTO` normalization to `PAC`
213    this.controller_.recalcFormValues_({
214      mode: ProxyFormController.ProxyTypes.AUTO,
215      rules: {},
216      pacScript: ''
217    });
218    this.assert(
219        document.getElementById(ProxyFormController.ProxyTypes.PAC)
220            .classList.contains('active'));
221
222    // DIRECT
223    this.controller_.recalcFormValues_({
224      mode: ProxyFormController.ProxyTypes.DIRECT,
225      rules: {},
226      pacScript: ''
227    });
228    this.assert(
229        document.getElementById(ProxyFormController.ProxyTypes.DIRECT)
230            .classList.contains('active'));
231
232    // FIXED
233    this.controller_.recalcFormValues_({
234      mode: ProxyFormController.ProxyTypes.FIXED,
235      rules: {},
236      pacScript: ''
237    });
238    this.assert(
239        document.getElementById(ProxyFormController.ProxyTypes.FIXED)
240            .classList.contains('active'));
241
242    // PAC
243    this.controller_.recalcFormValues_({
244      mode: ProxyFormController.ProxyTypes.PAC,
245      rules: {},
246      pacScript: ''
247    });
248    this.assert(
249        document.getElementById(ProxyFormController.ProxyTypes.PAC)
250          .classList.contains('active'));
251
252    // SYSTEM
253    this.controller_.recalcFormValues_({
254      mode: ProxyFormController.ProxyTypes.SYSTEM,
255      rules: {},
256      pacScript: ''
257    });
258    this.assert(
259        document.getElementById(ProxyFormController.ProxyTypes.SYSTEM)
260          .classList.contains('active'));
261  },
262
263  testRecalcFormValuesFixedSingle: function() {
264    this.controller_.recalcFormValues_({
265      mode: ProxyFormController.ProxyTypes.FIXED,
266      rules: {
267         singleProxy: {
268           scheme: 'socks5',
269           host: 'singleproxy.example.com',
270           port: '1234'
271        }
272      }
273    });
274    var single = this.controller_.singleProxy;
275    this.assertEqual('socks5', single.scheme);
276    this.assertEqual('singleproxy.example.com', single.host);
277    this.assertEqual(1234, single.port);
278  },
279
280  testRecalcFormValuesPacScript: function() {
281    this.controller_.recalcFormValues_({
282      mode: ProxyFormController.ProxyTypes.PAC,
283      rules: {},
284      pacScript: {url: 'http://example.com/this/is/a/pac.script'}
285    });
286    this.assertEqual(
287        'http://example.com/this/is/a/pac.script',
288        document.getElementById('autoconfigURL').value);
289  },
290
291  testRecalcFormValuesSingle: function() {
292    this.controller_.recalcFormValues_({
293       mode: ProxyFormController.ProxyTypes.FIXED,
294       rules: {
295         singleProxy: {
296           scheme: 'https',
297           host: 'example.com',
298           port: 80
299        }
300      }
301    });
302    // Single!
303    this.assert(
304      document.querySelector('#' + ProxyFormController.ProxyTypes.FIXED +
305          ' > section').classList.contains('single'));
306
307    var single = this.controller_.singleProxy;
308    this.assertEqual('https', single.scheme);
309    this.assertEqual('example.com', single.host);
310    this.assertEqual(80, single.port);
311  },
312
313  testRecalcFormValuesMultiple: function() {
314    this.controller_.recalcFormValues_({
315       mode: ProxyFormController.ProxyTypes.FIXED,
316       rules: {
317         proxyForHttp: {
318           scheme: 'http',
319           host: 'http.example.com',
320           port: 1
321        },
322         proxyForHttps: {
323           scheme: 'https',
324           host: 'https.example.com',
325           port: 2
326        },
327         proxyForFtp: {
328           scheme: 'socks4',
329           host: 'socks4.example.com',
330           port: 3
331        },
332         fallbackProxy: {
333           scheme: 'socks5',
334           host: 'socks5.example.com',
335           port: 4
336        }
337      }
338    });
339    // Not Single!
340    this.assert(
341      !document.querySelector('#' + ProxyFormController.ProxyTypes.FIXED
342          + ' > section').classList.contains('single'));
343    var server = this.controller_.singleProxy;
344    this.assertNull(server);
345
346    server = this.controller_.httpProxy;
347    this.assertEqual('http', server.scheme);
348    this.assertEqual('http.example.com', server.host);
349    this.assertEqual(1, server.port);
350
351    server = this.controller_.httpsProxy;
352    this.assertEqual('https', server.scheme);
353    this.assertEqual('https.example.com', server.host);
354    this.assertEqual(2, server.port);
355
356    server = this.controller_.ftpProxy;
357    this.assertEqual('socks4', server.scheme);
358    this.assertEqual('socks4.example.com', server.host);
359    this.assertEqual(3, server.port);
360
361    server = this.controller_.fallbackProxy;
362    this.assertEqual('socks5', server.scheme);
363    this.assertEqual('socks5.example.com', server.host);
364    this.assertEqual(4, server.port);
365  },
366
367  testBypassList: function() {
368    this.controller_.bypassList = ['1.example.com',
369                                   '2.example.com',
370                                   '3.example.com'];
371    this.assertEnumEqual(
372        document.getElementById('bypassList').value,
373        '1.example.com, 2.example.com, 3.example.com');
374    this.assertEnumEqual(
375        this.controller_.bypassList,
376        ['1.example.com', '2.example.com', '3.example.com']);
377  },
378
379  // Test that "system" rules are correctly generated
380  testProxyRulesGenerationSystem: function() {
381    this.controller_.changeActive_(
382        document.getElementById(ProxyFormController.ProxyTypes.SYSTEM));
383
384    this.assertHashEqual(
385        {mode: 'system'},
386        this.controller_.generateProxyConfig_());
387  },
388
389  // Test that "direct" rules are correctly generated
390  testProxyRulesGenerationDirect: function() {
391    this.controller_.changeActive_(
392        document.getElementById(ProxyFormController.ProxyTypes.DIRECT));
393
394    this.assertHashEqual(
395        {mode: 'direct'},
396        this.controller_.generateProxyConfig_());
397  },
398
399  // Test that auto detection rules are correctly generated when "automatic"
400  // is selected, and no PAC file URL is given
401  testProxyRulesGenerationAuto: function() {
402    this.controller_.changeActive_(
403        document.getElementById(ProxyFormController.ProxyTypes.PAC));
404
405    this.assertHashEqual(
406        {mode: 'auto_detect'},
407        this.controller_.generateProxyConfig_());
408  },
409
410  // Test that PAC URL rules are correctly generated when "automatic"
411  // is selected, and a PAC file URL is given
412  testProxyRulesGenerationPacURL: function() {
413    this.controller_.changeActive_(
414        document.getElementById(ProxyFormController.ProxyTypes.PAC));
415    this.controller_.pacURL = 'http://example.com/pac.pac';
416    var result = this.controller_.generateProxyConfig_();
417    this.assertEqual('pac_script', result.mode);
418    this.assertEqual('http://example.com/pac.pac', result.pacScript.url);
419  },
420
421  // Manual PAC definitions
422  testProxyRulesGenerationPacData: function() {
423    var pacData = 'function FindProxyForURL(url,host) { return "DIRECT"; }';
424    this.controller_.changeActive_(
425        document.getElementById(ProxyFormController.ProxyTypes.PAC));
426    this.controller_.manualPac = pacData;
427    var result = this.controller_.generateProxyConfig_();
428    this.assertEqual('pac_script', result.mode);
429    this.assertEqual(pacData, result.pacScript.data);
430  },
431
432  // PAC URLs override manual PAC definitions
433  testProxyRulesGenerationPacURLOverridesData: function() {
434    this.controller_.changeActive_(
435        document.getElementById(ProxyFormController.ProxyTypes.PAC));
436    this.controller_.pacURL = 'http://example.com/pac.pac';
437    this.controller_.manualPac =
438        'function FindProxyForURL(url,host) { return "DIRECT"; }';
439    var result = this.controller_.generateProxyConfig_();
440    this.assertEqual('pac_script', result.mode);
441    this.assertEqual('http://example.com/pac.pac', result.pacScript.url);
442  },
443
444  // Test that fixed, manual servers are correctly transformed into a
445  // `ProxyRules` structure.
446  testProxyRulesGenerationSingle: function() {
447    this.controller_.changeActive_(
448        document.getElementById(ProxyFormController.ProxyTypes.FIXED));
449
450    this.controller_.singleProxy = {
451      scheme: 'http',
452      host: 'example.com',
453      port: '80'
454    };
455
456    var result = this.controller_.generateProxyConfig_();
457    this.assertEqual('fixed_servers', result.mode);
458    this.assertEqual('http', result.rules.singleProxy.scheme);
459    this.assertEqual('example.com', result.rules.singleProxy.host);
460    this.assertEqual(80, result.rules.singleProxy.port);
461    this.assertEqual(undefined, result.rules.proxyForHttp);
462    this.assertEqual(undefined, result.rules.proxyForHttps);
463    this.assertEqual(undefined, result.rules.proxyForFtp);
464    this.assertEqual(undefined, result.rules.fallbackProxy);
465  },
466
467  // Test that proxy configuration rules are correctly generated
468  // for separate manually entered servers.
469  testProxyRulesGenerationSeparate: function() {
470    this.controller_.changeActive_(
471        document.getElementById(ProxyFormController.ProxyTypes.FIXED));
472
473    this.controller_.singleProxy = false;
474    this.controller_.httpProxy = {
475      scheme: 'http',
476      host: 'http.example.com',
477      port: 80
478    };
479    this.controller_.httpsProxy = {
480      scheme: 'https',
481      host: 'https.example.com',
482      port: 443
483    };
484    this.controller_.ftpProxy = {
485      scheme: 'socks4',
486      host: 'ftp.example.com',
487      port: 80
488    };
489    this.controller_.fallbackProxy = {
490      scheme: 'socks5',
491      host: 'fallback.example.com',
492      port: 80
493    };
494
495    var result = this.controller_.generateProxyConfig_();
496    this.assertEqual('fixed_servers', result.mode);
497    this.assertEqual(undefined, result.rules.singleProxy);
498    this.assertEqual('http', result.rules.proxyForHttp.scheme);
499    this.assertEqual('http.example.com', result.rules.proxyForHttp.host);
500    this.assertEqual('80', result.rules.proxyForHttp.port);
501    this.assertEqual('https', result.rules.proxyForHttps.scheme);
502    this.assertEqual('https.example.com', result.rules.proxyForHttps.host);
503    this.assertEqual('443', result.rules.proxyForHttps.port);
504    this.assertEqual('socks4', result.rules.proxyForFtp.scheme);
505    this.assertEqual('ftp.example.com', result.rules.proxyForFtp.host);
506    this.assertEqual('80', result.rules.proxyForFtp.port);
507    this.assertEqual('socks5', result.rules.fallbackProxy.scheme);
508    this.assertEqual('fallback.example.com', result.rules.fallbackProxy.host);
509    this.assertEqual('80', result.rules.fallbackProxy.port);
510  }
511}, { testLog: 'proxyformcontrollerlog' });
512
513var c = new ProxyFormController('proxyForm');
514