#!/usr/bin/env python
#
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = 'mike.aizatsky@gmail.com (Mike Aizatsky)'

import base64
import os
import unittest
import urllib

from google.appengine.ext import db
from afterburner.experimental.db import triggers
from afterburner.testing import test_base


class Foo(db.Model):
  """Test entity type."""
  pass


class Bar(db.Model):
  """Test entity type."""
  pass


def decode_task_payload(task):
  """Decodes POST task payload.

  Args:
    task: a task to decode its payload.

  Returns:
    parameter_name -> parameter_value dict.
  """
  key_values = [kv.split("=") for kv in
                base64.b64decode(task["body"]).split("&")]
  return dict((key, urllib.unquote_plus(value)) for key, value in key_values)


class TriggersTestCase(test_base.TestBase):
  """Base class for triggers tests."""

  def setUp(self):
    super(TriggersTestCase, self).setUp()
    triggers.TriggerManager.init()


class TriggerManagerTest(TriggersTestCase):

  def setUp(self):
    TriggersTestCase.setUp(self)
    triggers.TriggerManager.register_trigger(Foo.kind(), "foo")

  def deleteAllUpdates(self):
    for update in triggers.Update.all():
      update.delete()

  def assertHasUpdate(self, key):
    """Verifies that update entity was created."""
    self.assertTrue(
        triggers.Update.get_by_key_name(str(key), parent=key))

  def assertHasNoUpdate(self, key):
    """Verifies that no update entity was created."""
    self.assertFalse(
        triggers.Update.get_by_key_name(str(key), parent=key))

  def assertHasUpdateTask(self, key, queue="default"):
    tasks = self.taskqueue.GetTasks(queue)
    for task in tasks:
      if task["url"] != triggers.UPDATE_URL:
        continue
      payload = decode_task_payload(task)
      if payload["key"] == str(key):
        return
    self.fail("Update task for key not found: %s" % key)

  def testUpdateQueue_SinglePut(self):
    """Test queueing update after single put statement."""
    key = Foo().put()
    self.assertHasUpdate(key)

  def testUpdateQueue_MultiplePuts(self):
    """Test queueing update after multiple entities in put."""
    foos = [Foo(), Foo(), Foo()]
    keys = db.put(foos)
    for key in keys:
      self.assertHasUpdate(key)

  def testUpdateQueue_PutInTransaction(self):
    """Test queueing update after single put in transaction."""
    def tx():
      return Foo().put()
    key = db.run_in_transaction(tx)
    self.assertHasUpdate(key)

  def testUpdateQueue_MultiplePutsInTransaction(self):
    """Test queueing update after multiple puts in transaction."""
    def tx():
      # TODO: right now we can't support more than 5 tasks in a single
      # transactions. Test >5 as soon as this limitation is lifted.
      foos = [Foo(parent=db.Key.from_path("Root", "root")) for i in range(30) ]
      return db.put(foos)
    keys = db.run_in_transaction(tx)
    for key in keys:
      self.assertHasUpdate(key)

  def testUpdateQueue_SingleDelete(self):
    """Test queueing update after single delete."""
    key = Foo().put()
    self.deleteAllUpdates()
    db.delete(key)
    self.assertHasUpdate(key)

  def testUpdateQueue_MultipleDeletes(self):
    """Test queueing update task after multiple deletes."""
    foos = [Foo(), Foo(), Foo()]
    keys = db.put(foos)
    self.deleteAllUpdates()

    db.delete(keys)
    for key in keys:
      self.assertHasUpdate(key)

  def testUpdateQueue_DeleteInTransaction(self):
    """Test queueing update task after single delete."""
    key = Foo().put()
    self.deleteAllUpdates()

    db.run_in_transaction(db.delete, key)
    self.assertHasUpdate(key)

  def testIgnoreUnregisteredEntities(self):
    key = Bar().put()
    self.assertHasNoUpdate(key)

  def testProcessUpdates_NoUpdates(self):
    triggers.TriggerManager.process_updates()
    self.assertEquals([], self.taskqueue.GetTasks("default"))
    self.assertEquals([], triggers.Update.all().fetch(100))

  def testProcessUpdates_UpdatesPresent(self):
    foos = [Foo(), Foo(), Foo()]
    keys = db.put(foos)
    triggers.TriggerManager.process_updates()
    self.assertEquals([], triggers.Update.all().fetch(100))
    for key in keys:
      self.assertHasUpdateTask(key)


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