# Copyright 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

"""Unit tests for nss_cache/files_updater.py."""

__author__ = ('vasilios@google.com (V Hoffman)',
              'jaq@google.com (Jamie Wilkinson)',
              'blaedd@google.com (David MacKinnon)')


import os
import shutil
import tempfile
import time
import unittest

import mox

from nss_cache import config
from nss_cache import error

from nss_cache.caches import cache_factory
from nss_cache.caches import files
from nss_cache.maps import automount
from nss_cache.maps import passwd
from nss_cache.sources import source
from nss_cache.sources import zsyncsource

from nss_cache.update import files_updater


class SingleFileUpdaterTest(mox.MoxTestBase):
  """Unit tests for FileMapUpdaterlass."""

  def setUp(self):
    super(SingleFileUpdaterTest, self).setUp()
    self.workdir = tempfile.mkdtemp()
    self.workdir2 = tempfile.mkdtemp()

  def tearDown(self):
    super(SingleFileUpdaterTest, self).tearDown()
    shutil.rmtree(self.workdir)
    shutil.rmtree(self.workdir2)

  def testFullUpdate(self):
    original_modify_stamp = time.gmtime(1)
    new_modify_stamp = time.gmtime(2)

    # Construct a fake source.
    def GetFile(map_name, dst_file, current_file, location):
      print "GetFile: %s" % dst_file
      f = open(dst_file, 'w')
      f.write('root:x:0:0:root:/root:/bin/bash\n')
      f.close()
      os.utime(dst_file, (1, 2))
      os.system("ls -al %s" % dst_file)
      return dst_file

    dst_file = mox.Value()
    source_mock = self.mox.CreateMock(source.FileSource)
    source_mock.GetFile(config.MAP_PASSWORD,
                        mox.Remember(dst_file),
                        current_file=mox.IgnoreArg(),
                        location=mox.IgnoreArg()
                       ).WithSideEffects(GetFile).AndReturn(dst_file)

    # Construct the cache.
    cache = files.FilesPasswdMapHandler({'dir': self.workdir2})
    map_entry = passwd.PasswdMapEntry({'name': 'foo', 'uid': 10, 'gid': 10})
    password_map = passwd.PasswdMap()
    password_map.SetModifyTimestamp(new_modify_stamp)
    password_map.Add(map_entry)
    cache.Write(password_map)

    updater = files_updater.FileMapUpdater(config.MAP_PASSWORD,
                                           self.workdir,
                                           {'name': 'files',
                                            'dir': self.workdir2})
    updater.WriteModifyTimestamp(original_modify_stamp)

    self.mox.ReplayAll()

    self.assertEqual(0, updater.UpdateCacheFromSource(cache,
                                                      source_mock,
                                                      force_write=False,
                                                      location=None))

    self.assertEqual(new_modify_stamp, updater.GetModifyTimestamp())
    self.assertNotEqual(None, updater.GetUpdateTimestamp())

  def testFullUpdateOnEmptyCache(self):
    """A full update as above, but the initial cache is empty."""
    original_modify_stamp = time.gmtime(1)
    new_modify_stamp = time.gmtime(2)
    # Construct an updater
    self.updater = files_updater.FileMapUpdater(config.MAP_PASSWORD,
                                                self.workdir,
                                                {'name': 'files',
                                                 'dir': self.workdir2})
    self.updater.WriteModifyTimestamp(original_modify_stamp)

    # Construct a cache
    cache = files.FilesPasswdMapHandler({'dir': self.workdir2})

    def GetFileEffects(map_name, dst_file, current_file, location):
      f = open(dst_file, 'w')
      f.write('root:x:0:0:root:/root:/bin/bash\n')
      f.close()
      os.utime(dst_file, (1, 2))
      return dst_file

    source_mock = self.mox.CreateMock(source.FileSource)
    source_mock.GetFile(config.MAP_PASSWORD, mox.IgnoreArg(), mox.IgnoreArg(), location=None).WithSideEffects(GetFileEffects)

    #source_mock = MockSource()
    self.assertEqual(0, self.updater.UpdateCacheFromSource(cache,
                                                           source_mock,
                                                           force_write=False,
                                                           location=None))
    self.assertEqual(new_modify_stamp, self.updater.GetModifyTimestamp())
    self.assertNotEqual(None, self.updater.GetUpdateTimestamp())

  def testFullUpdateOnEmptySource(self):
    """A full update as above, but instead, the initial source is empty."""
    original_modify_stamp = time.gmtime(1)
    new_modify_stamp = time.gmtime(2)
    # Construct an updater
    self.updater = files_updater.FileMapUpdater(config.MAP_PASSWORD,
                                                self.workdir,
                                                {'name': 'files',
                                                 'dir': self.workdir2})
    self.updater.WriteModifyTimestamp(original_modify_stamp)

    # Construct a cache
    cache = files.FilesPasswdMapHandler({'dir': self.workdir2})
    map_entry = passwd.PasswdMapEntry({'name': 'foo', 'uid': 10, 'gid': 10})
    password_map = passwd.PasswdMap()
    password_map.SetModifyTimestamp(new_modify_stamp)
    password_map.Add(map_entry)
    cache.Write(password_map)

    source_mock = self.mox.CreateMock(source.FileSource)
    source_mock.GetFile(config.MAP_PASSWORD,
                        mox.IgnoreArg(),
                        current_file=mox.IgnoreArg(),
                        location=None).AndReturn(None)
    self.mox.ReplayAll()
    self.assertRaises(error.EmptyMap,
                      self.updater.UpdateCacheFromSource,
                      cache,
                      source_mock,
                      force_write=False,
                      location=None)
    self.assertNotEqual(new_modify_stamp, self.updater.GetModifyTimestamp())
    self.assertEqual(None, self.updater.GetUpdateTimestamp())

  # def disabled_testFullUpdateOnEmptySourceForceWrite(self):
  #   """A full update as above, but instead, the initial source is empty."""
  #   original_modify_stamp = time.gmtime(1)
  #   new_modify_stamp = time.gmtime(2)
  #   # Construct an updater
  #   self.updater = files_updater.FileMapUpdater(config.MAP_PASSWORD,
  #                                                self.workdir,
  #                                                {'name': 'files',
  #                                                 'dir': self.workdir2})
  #   self.updater.WriteModifyTimestamp(original_modify_stamp)

  #   # Construct a cache
  #   cache = files.FilesPasswdMapHandler({'dir': self.workdir2})
  #   map_entry = passwd.PasswdMapEntry({'name': 'foo', 'uid': 10, 'gid': 10})
  #   password_map = passwd.PasswdMap()
  #   password_map.SetModifyTimestamp(new_modify_stamp)
  #   password_map.Add(map_entry)
  #   cache.Write(password_map)

  #   class MockSource(pmock.Mock):
  #     def GetFile(self, map_name, dst_file, current_file, location=None):
  #       assert location is None
  #       assert map_name == config.MAP_PASSWORD
  #       f = open(dst_file, 'w')
  #       f.write('')
  #       f.close()
  #       os.utime(dst_file, (1, 2))
  #       return dst_file

  #   source_mock = MockSource()
  #   self.assertEqual(0, self.updater.UpdateCacheFromSource(cache,
  #                                                          source_mock,
  #                                                          force_write=True,
  #                                                          location=None))
  #   self.assertEqual(new_modify_stamp, self.updater.GetModifyTimestamp())
  #   self.assertNotEqual(None, self.updater.GetUpdateTimestamp())


# class AutomountUpdaterTest(mox.MoxTestBase):
#   """Unit tests for FileAutomountUpdater class."""

#   def setUp(self):
#     super(AutomountUpdaterTest, self).setUp()
#     self.workdir = tempfile.mkdtemp()

#   def tearDown(self):
#     shutil.rmtree(self.workdir)
#     super(AutomountUpdaterTest, self).tearDown()

#   def testInit(self):
#     """An automount object correctly sets map-specific attributes."""
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT,
#                                             self.workdir, {})
#     self.assertEqual(updater.local_master, False)

#     conf = {files_updater.FileAutomountUpdater.OPT_LOCAL_MASTER: 'yes'}
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT,
#                                             self.workdir, conf)
#     self.assertEqual(updater.local_master, True)

#     conf = {files_updater.FileAutomountUpdater.OPT_LOCAL_MASTER: 'no'}
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT,
#                                             self.workdir, conf)
#     self.assertEqual(updater.local_master, False)

#   def testUpdate(self):
#     """An update gets a master map and updates each entry."""
#     map_entry1 = automount.AutomountMapEntry()
#     map_entry2 = automount.AutomountMapEntry()
#     map_entry1.key = '/home'
#     map_entry2.key = '/auto'
#     map_entry1.location = 'ou=auto.home,ou=automounts'
#     map_entry2.location = 'ou=auto.auto,ou=automounts'
#     master_map = automount.AutomountMap([map_entry1, map_entry2])

#     source_mock = self.mox.CreateMock(zsyncsource.ZSyncSource)
#     source_mock.GetAutomountMasterFile(mox.IgnoreArg()).AndReturn(master_map)

#     # the auto.home cache
#     cache_mock1 = self.mox.CreateMock(files.FilesCache)
#     cache_mock1.GetCacheFilename().AndReturn(None)
#     cache_mock1.GetMapLocation().AndReturn('/etc/auto.home')

#     # the auto.auto cache
#     cache_mock2 = self.mox.CreateMock(files.FilesCache)
#     cache_mock2.GetMapLocation().AndReturn('/etc/auto.auto')
#     cache_mock2.GetCacheFilename().AndReturn(None)

#     # the auto.master cache
#     cache_mock3 = self.mox.CreateMock(files.FilesCache)
#     cache_mock3.GetMap().AndReturn(master_map)

#     self.mox.StubOutWithMock(cache_factory, 'Create')
#     cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), None).AndReturn(cache_mock3)
#     cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), automount_mountpoint='/auto').AndReturn(cache_mock2)
#     cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), automount_mountpoint='/home').AndReturn(cache_mock1)

#     self.mox.ReplayAll()

#     options = {'name': 'files', 'dir': self.workdir}
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT,
#                                             self.workdir, options)
#     updater.UpdateFromSource(source_mock)

#     self.assertEqual(map_entry1.location, '/etc/auto.home')
#     self.assertEqual(map_entry2.location, '/etc/auto.auto')

#   def testUpdateNoMaster(self):
#     """An update skips updating the master map, and approprate sub maps."""
#     source_entry1 = automount.AutomountMapEntry()
#     source_entry2 = automount.AutomountMapEntry()
#     source_entry1.key = '/home'
#     source_entry2.key = '/auto'
#     source_entry1.location = 'ou=auto.home,ou=automounts'
#     source_entry2.location = 'ou=auto.auto,ou=automounts'
#     source_master = automount.AutomountMap([source_entry1, source_entry2])

#     local_entry1 = automount.AutomountMapEntry()
#     local_entry2 = automount.AutomountMapEntry()
#     local_entry1.key = '/home'
#     local_entry2.key = '/auto'
#     local_entry1.location = '/etc/auto.home'
#     local_entry2.location = '/etc/auto.null'
#     local_master = automount.AutomountMap([local_entry1, local_entry2])
#     source_mock = self.mock()
#     invocation = source_mock.expects(pmock.at_least_once())
#     invocation._CalledUpdateCacheFromSource()
#     # we should get called inside the DummyUpdater, too.

#     # the auto.home cache
#     cache_mock1 = self.mock()
#     # GetMapLocation() is called, and set to the master map map_entry
#     invocation = cache_mock1.expects(pmock.at_least_once()).GetMapLocation()
#     invocation.will(pmock.return_value('/etc/auto.home'))
#     # we should get called inside the DummyUpdater
#     cache_mock1.expects(pmock.at_least_once())._CalledUpdateCacheFromSource()

#     # the auto.auto cache
#     cache_mock2 = self.mock()
#     # GetMapLocation() is called, and set to the master map map_entry
#     invocation = cache_mock2.expects(pmock.at_least_once()).GetMapLocation()
#     invocation.will(pmock.return_value('/etc/auto.auto'))
#     invocation = cache_mock2.expects(
#         pmock.at_least_once())._CalledUpdateCacheFromSource()
#     # the auto.master cache, which should not be written to
#     cache_mock3 = self.mock()
#     invocation = cache_mock3.expects(pmock.once())
#     invocation = invocation.method('GetMap')
#     invocation.will(pmock.return_value(local_master))
#     invocation = cache_mock3.expects(pmock.once())
#     invocation = invocation.method('GetMap')
#     invocation.will(pmock.return_value(local_master))

#     cache_mocks = {'/home': cache_mock1, '/auto': cache_mock2,
#                    None: cache_mock3}

#     # Create needs to return our mock_caches
#     def DummyCreate(unused_cache_options, unused_map_name,
#                     automount_mountpoint=None):
#       # the order of the master_map iterable is not predictable, so we use the
#       # automount_mountpoint as the key to return the right one.
#       return cache_mocks[automount_mountpoint]

#     original_create = cache_factory.Create
#     cache_factory.Create = DummyCreate

#     skip = files_updater.FileAutomountUpdater.OPT_LOCAL_MASTER
#     options = {skip: 'yes', 'dir': self.workdir}
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT, self.workdir,
#                                             options)
#     updater.UpdateFromSource(source_mock)

#     cache_factory.Create = original_create

#   def testUpdateCatchesMissingMaster(self):
#     """Gracefully handle a missing local master map."""
#     # use an empty master map from the source, to avoid mocking out already
#     # tested code
#     source_mock = self.mock()

#     cache_mock = self.mock()
#     # raise error on GetMap()
#     invocation = cache_mock.expects(pmock.once()).GetMap()
#     invocation.will(pmock.raise_exception(error.CacheNotFound))

#     # Create needs to return our mock_cache
#     def DummyCreate(unused_cache_options, unused_map_name,
#                     automount_mountpoint=None):
#       # the order of the master_map iterable is not predictable, so we use the
#       # automount_mountpoint as the key to return the right one.
#       return cache_mock

#     original_create = cache_factory.Create
#     cache_factory.Create = DummyCreate

#     skip = files_updater.FileAutomountUpdater.OPT_LOCAL_MASTER
#     options = {skip: 'yes', 'dir': self.workdir}
#     updater = files_updater.FileAutomountUpdater(config.MAP_AUTOMOUNT, self.workdir,
#                                             options)

#     return_value = updater.UpdateFromSource(source_mock)

#     self.assertEqual(return_value, 1)

#     cache_factory.Create = original_create


if __name__ == '__main__':
  unittest.main()
