1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import json
7import os
8import unittest
9
10from jsc_view import GetEventByNameFromEvents
11from api_schema_graph import APISchemaGraph
12from availability_finder import AvailabilityFinder, AvailabilityInfo
13from branch_utility import BranchUtility, ChannelInfo
14from compiled_file_system import CompiledFileSystem
15from extensions_paths import CHROME_EXTENSIONS
16from fake_host_file_system_provider import FakeHostFileSystemProvider
17from fake_url_fetcher import FakeUrlFetcher
18from features_bundle import FeaturesBundle
19from future import Future
20from host_file_system_iterator import HostFileSystemIterator
21from jsc_view import JSCView, _FormatValue
22from object_store_creator import ObjectStoreCreator
23from schema_processor import SchemaProcessorFactoryForTest
24from server_instance import ServerInstance
25from test_data.api_data_source.canned_master_fs import CANNED_MASTER_FS_DATA
26from test_data.canned_data import CANNED_API_FILE_SYSTEM_DATA
27from test_data.object_level_availability.tabs import TABS_SCHEMA_BRANCHES
28from test_file_system import TestFileSystem
29from test_util import Server2Path
30
31
32class _FakeTemplateCache(object):
33
34  def GetFromFile(self, key):
35    return Future(value='motemplate %s' % key)
36
37
38class _FakeFeaturesBundle(object):
39  def GetAPIFeatures(self):
40    return Future(value={
41      'bluetooth': {'value': True},
42      'contextMenus': {'value': True},
43      'jsonStableAPI': {'value': True},
44      'idle': {'value': True},
45      'input.ime': {'value': True},
46      'tabs': {'value': True}
47    })
48
49
50class _FakeAvailabilityFinder(object):
51  def __init__(self, fake_availability):
52    self._fake_availability = fake_availability
53
54  def GetAPIAvailability(self, api_name):
55    return self._fake_availability
56
57  def GetAPINodeAvailability(self, api_name):
58    schema_graph = APISchemaGraph()
59    api_graph = APISchemaGraph(json.loads(
60        CANNED_MASTER_FS_DATA['api'][api_name + '.json']))
61    # Give the graph fake ChannelInfo; it's not used in tests.
62    channel_info = ChannelInfo('stable', '28', 28)
63    schema_graph.Update(api_graph, lambda _: channel_info)
64    return schema_graph
65
66
67class JSCViewTest(unittest.TestCase):
68  def setUp(self):
69    self._base_path = Server2Path('test_data', 'test_json')
70
71    server_instance = ServerInstance.ForTest(
72        TestFileSystem(CANNED_MASTER_FS_DATA, relative_to=CHROME_EXTENSIONS))
73    file_system = server_instance.host_file_system_provider.GetMaster()
74    self._json_cache = server_instance.compiled_fs_factory.ForJson(file_system)
75    self._features_bundle = FeaturesBundle(file_system,
76                                           server_instance.compiled_fs_factory,
77                                           server_instance.object_store_creator,
78                                           'extensions')
79    self._api_models = server_instance.platform_bundle.GetAPIModels(
80        'extensions')
81    self._fake_availability = AvailabilityInfo(ChannelInfo('stable', '396', 5))
82
83  def _ReadLocalFile(self, filename):
84    with open(os.path.join(self._base_path, filename), 'r') as f:
85      return f.read()
86
87  def _LoadJSON(self, filename):
88    return json.loads(self._ReadLocalFile(filename))
89
90  def _FakeLoadAddRulesSchema(self):
91    events = self._LoadJSON('add_rules_def_test.json')
92    return Future(value=GetEventByNameFromEvents(events))
93
94  def testFormatValue(self):
95    self.assertEquals('1,234,567', _FormatValue(1234567))
96    self.assertEquals('67', _FormatValue(67))
97    self.assertEquals('234,567', _FormatValue(234567))
98
99  def testGetEventByNameFromEvents(self):
100    events = {}
101    # Missing 'types' completely.
102    self.assertRaises(AssertionError, GetEventByNameFromEvents, events)
103
104    events['types'] = []
105    # No type 'Event' defined.
106    self.assertRaises(AssertionError, GetEventByNameFromEvents, events)
107
108    events['types'].append({ 'name': 'Event',
109                             'functions': []})
110    add_rules = { "name": "addRules" }
111    events['types'][0]['functions'].append(add_rules)
112    self.assertEqual(add_rules,
113                     GetEventByNameFromEvents(events)['addRules'])
114
115    events['types'][0]['functions'].append(add_rules)
116    # Duplicates are an error.
117    self.assertRaises(AssertionError, GetEventByNameFromEvents, events)
118
119  def testCreateId(self):
120    fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
121    dict_ = JSCView(self._api_models.GetContentScriptAPIs().Get(),
122                    self._api_models.GetModel('tester').Get(),
123                    fake_avail_finder,
124                    self._json_cache,
125                    _FakeTemplateCache(),
126                    self._features_bundle,
127                    None,
128                    'extensions').ToDict()
129    self.assertEquals('type-TypeA', dict_['types'][0]['id'])
130    self.assertEquals('property-TypeA-b',
131                      dict_['types'][0]['properties'][0]['id'])
132    self.assertEquals('method-get', dict_['functions'][0]['id'])
133    self.assertEquals('event-EventA', dict_['events'][0]['id'])
134
135  # TODO(kalman): re-enable this when we have a rebase option.
136  def DISABLED_testToDict(self):
137    fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
138    expected_json = self._LoadJSON('expected_tester.json')
139    dict_ = JSCView(self._api_models.GetContentScriptAPIs().Get(),
140                    self._api_models.GetModel('tester').Get(),
141                    fake_avail_finder,
142                    self._json_cache,
143                    _FakeTemplateCache(),
144                    self._features_bundle,
145                    None,
146                    'extensions').ToDict()
147    self.assertEquals(expected_json, dict_)
148
149  def testAddRules(self):
150    fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
151    dict_ = JSCView(self._api_models.GetContentScriptAPIs().Get(),
152                    self._api_models.GetModel('add_rules_tester').Get(),
153                    fake_avail_finder,
154                    self._json_cache,
155                    _FakeTemplateCache(),
156                    self._features_bundle,
157                    self._FakeLoadAddRulesSchema(),
158                    'extensions').ToDict()
159
160    # Check that the first event has the addRulesFunction defined.
161    self.assertEquals('add_rules_tester', dict_['name'])
162    self.assertEquals('rules', dict_['events'][0]['name'])
163    self.assertEquals('notable_name_to_check_for',
164                      dict_['events'][0]['byName']['addRules'][
165                          'parameters'][0]['name'])
166
167    # Check that the second event has addListener defined.
168    self.assertEquals('noRules', dict_['events'][1]['name'])
169    self.assertEquals('add_rules_tester', dict_['name'])
170    self.assertEquals('noRules', dict_['events'][1]['name'])
171    self.assertEquals('callback',
172                      dict_['events'][0]['byName']['addListener'][
173                          'parameters'][0]['name'])
174
175  def testGetIntroList(self):
176    fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
177    model = JSCView(self._api_models.GetContentScriptAPIs().Get(),
178                    self._api_models.GetModel('tester').Get(),
179                    fake_avail_finder,
180                    self._json_cache,
181                    _FakeTemplateCache(),
182                    self._features_bundle,
183                    None,
184                    'extensions')
185    expected_list = [
186      { 'title': 'Description',
187        'content': [
188          { 'text': 'a test api' }
189        ]
190      },
191      { 'title': 'Availability',
192        'content': [
193          { 'partial': 'motemplate chrome/common/extensions/docs/' +
194                       'templates/private/intro_tables/stable_message.html',
195            'version': 5,
196            'scheduled': None
197          }
198        ]
199      },
200      { 'title': 'Permissions',
201        'content': [
202          { 'class': 'override',
203            'text': '"tester"'
204          },
205          { 'text': 'is an API for testing things.' }
206        ]
207      },
208      { 'title': 'Manifest',
209        'content': [
210          { 'class': 'code',
211            'text': '"tester": {...}'
212          }
213        ]
214      },
215      { 'title': 'Content Scripts',
216        'content': [
217          {
218            'partial': 'motemplate chrome/common/extensions/docs' +
219                       '/templates/private/intro_tables/content_scripts.html',
220            'contentScriptSupport': {
221              'name': 'tester',
222              'restrictedTo': None
223            }
224          }
225        ]
226      },
227      { 'title': 'Learn More',
228        'content': [
229          { 'link': 'https://tester.test.com/welcome.html',
230            'text': 'Welcome!'
231          }
232        ]
233      }
234    ]
235    self.assertEquals(model._GetIntroTableList(), expected_list)
236
237    # Tests the same data with a scheduled availability.
238    fake_avail_finder = _FakeAvailabilityFinder(
239        AvailabilityInfo(ChannelInfo('beta', '1453', 27), scheduled=28))
240    model = JSCView(self._api_models.GetContentScriptAPIs().Get(),
241                    self._api_models.GetModel('tester').Get(),
242                    fake_avail_finder,
243                    self._json_cache,
244                    _FakeTemplateCache(),
245                    self._features_bundle,
246                    None,
247                    'extensions')
248    expected_list[1] = {
249      'title': 'Availability',
250      'content': [
251        { 'partial': 'motemplate chrome/common/extensions/docs/' +
252                     'templates/private/intro_tables/beta_message.html',
253          'version': 27,
254          'scheduled': 28
255        }
256      ]
257    }
258    self.assertEquals(model._GetIntroTableList(), expected_list)
259
260
261class JSCViewWithoutNodeAvailabilityTest(unittest.TestCase):
262  def setUp(self):
263    server_instance = ServerInstance.ForTest(
264        file_system_provider=FakeHostFileSystemProvider(
265            CANNED_API_FILE_SYSTEM_DATA))
266    self._api_models = server_instance.platform_bundle.GetAPIModels(
267        'extensions')
268    self._json_cache = server_instance.compiled_fs_factory.ForJson(
269        server_instance.host_file_system_provider.GetMaster())
270    self._avail_finder = server_instance.platform_bundle.GetAvailabilityFinder(
271        'extensions')
272
273
274  def testGetAPIAvailability(self):
275    api_availabilities = {
276      'bluetooth': 31,
277      'contextMenus': 'master',
278      'jsonStableAPI': 20,
279      'idle': 5,
280      'input.ime': 18,
281      'tabs': 18
282    }
283    for api_name, availability in api_availabilities.iteritems():
284      model_dict = JSCView(
285          self._api_models.GetContentScriptAPIs().Get(),
286          self._api_models.GetModel(api_name).Get(),
287          self._avail_finder,
288          self._json_cache,
289          _FakeTemplateCache(),
290          _FakeFeaturesBundle(),
291          None,
292          'extensions').ToDict()
293      self.assertEquals(availability,
294                        model_dict['introList'][1]['content'][0]['version'])
295
296
297class JSCViewWithNodeAvailabilityTest(unittest.TestCase):
298  def setUp(self):
299    tabs_unmodified_versions = (16, 20, 23, 24)
300    self._branch_utility = BranchUtility(
301        os.path.join('branch_utility', 'first.json'),
302        os.path.join('branch_utility', 'second.json'),
303        FakeUrlFetcher(Server2Path('test_data')),
304        ObjectStoreCreator.ForTest())
305    self._node_fs_creator = FakeHostFileSystemProvider(TABS_SCHEMA_BRANCHES)
306    self._node_fs_iterator = HostFileSystemIterator(self._node_fs_creator,
307                                                    self._branch_utility)
308    test_object_store = ObjectStoreCreator.ForTest()
309    self._avail_finder = AvailabilityFinder(
310        self._branch_utility,
311        CompiledFileSystem.Factory(test_object_store),
312        self._node_fs_iterator,
313        self._node_fs_creator.GetMaster(),
314        test_object_store,
315        'extensions',
316        SchemaProcessorFactoryForTest())
317
318    server_instance = ServerInstance.ForTest(
319        file_system_provider=FakeHostFileSystemProvider(
320            TABS_SCHEMA_BRANCHES))
321    self._api_models = server_instance.platform_bundle.GetAPIModels(
322        'extensions')
323    self._json_cache = server_instance.compiled_fs_factory.ForJson(
324        server_instance.host_file_system_provider.GetMaster())
325
326    # Imitate the actual SVN file system by incrementing the stats for paths
327    # where an API schema has changed.
328    last_stat = type('last_stat', (object,), {'val': 0})
329
330    def stat_paths(file_system, channel_info):
331      if channel_info.version not in tabs_unmodified_versions:
332        last_stat.val += 1
333      # HACK: |file_system| is a MockFileSystem backed by a TestFileSystem.
334      # Increment the TestFileSystem stat count.
335      file_system._file_system.IncrementStat(by=last_stat.val)
336      # Continue looping. The iterator will stop after 'master' automatically.
337      return True
338
339    # Use the HostFileSystemIterator created above to change global stat values
340    # for the TestFileSystems that it creates.
341    self._node_fs_iterator.Ascending(
342        # The earliest version represented with the tabs' test data is 13.
343        self._branch_utility.GetStableChannelInfo(13),
344        stat_paths)
345
346  def testGetAPINodeAvailability(self):
347    def assertEquals(node, actual):
348      node_availabilities = {
349          'tabs.Tab': None,
350          'tabs.fakeTabsProperty1': None,
351          'tabs.get': None,
352          'tabs.onUpdated': None,
353          'tabs.InjectDetails': 25,
354          'tabs.fakeTabsProperty2': 15,
355          'tabs.getCurrent': 19,
356          'tabs.onActivated': 30
357      }
358      self.assertEquals(node_availabilities[node], actual)
359
360    model_dict = JSCView(
361        self._api_models.GetContentScriptAPIs().Get(),
362        self._api_models.GetModel('tabs').Get(),
363        self._avail_finder,
364        self._json_cache,
365        _FakeTemplateCache(),
366        _FakeFeaturesBundle(),
367        None,
368        'extensions').ToDict()
369
370    # Test nodes that have the same availability as their parent.
371
372    # Test type.
373    assertEquals('tabs.Tab', model_dict['types'][0]['availability'])
374    # Test property.
375    assertEquals('tabs.fakeTabsProperty1',
376                 model_dict['properties'][0]['availability'])
377    # Test function.
378    assertEquals('tabs.get', model_dict['functions'][1]['availability'])
379    # Test event.
380    assertEquals('tabs.onUpdated', model_dict['events'][1]['availability'])
381
382    # Test nodes with varying availabilities.
383
384    # Test type.
385    assertEquals('tabs.InjectDetails',
386                 model_dict['types'][1]['availability']['version'])
387    # Test property.
388    assertEquals('tabs.fakeTabsProperty2',
389                 model_dict['properties'][2]['availability']['version'])
390    # Test function.
391    assertEquals('tabs.getCurrent',
392                 model_dict['functions'][0]['availability']['version'])
393    # Test event.
394    assertEquals('tabs.onActivated',
395                 model_dict['events'][0]['availability']['version'])
396
397    # Test a node that became deprecated.
398    self.assertEquals({
399      'scheduled': None,
400      'version': 26,
401      'partial': 'motemplate chrome/common/extensions/docs/templates/' +
402          'private/intro_tables/deprecated_message.html'
403      }, model_dict['types'][2]['availability'])
404
405if __name__ == '__main__':
406  unittest.main()
407