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