##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test add-form

$Id: test_add.py 107376 2009-12-30 19:21:24Z faassen $
"""
import unittest

from zope.browser.interfaces import IAdding
from zope.component import getMultiAdapter
from zope.component.eventtesting import getEvents
from zope.component.interfaces import IFactory
from zope.component.interfaces import IComponentLookup
from zope.component.factory import Factory
from zope.interface import Interface, implements
from zope.lifecycleevent.interfaces import IObjectCreatedEvent, IObjectModifiedEvent
from zope.publisher.browser import TestRequest
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.schema import TextLine, accessors
from zope.security.checker import CheckerPublic
from zope.site.site import SiteManagerAdapter

from zope.formlib.widget import CustomWidgetFactory
from zope.app.form.browser import TextWidget as Text
from zope.app.form.browser.add import AddViewFactory, AddView
from zope.app.form.browser.metaconfigure import AddFormDirective
from zope.app.form.browser.submit import Update
from zope.app.testing import ztapi

# Foo needs to be imported as globals() are checked
from zope.app.form.browser.tests.test_editview import IFoo, IBar, Foo
from zope.app.form.browser.tests.test_editview import FooBarAdapter

from zope.component.testing import PlacelessSetup as CAPlacelessSetup
from zope.component.eventtesting import PlacelessSetup as EventPlacelessSetup

class PlacelessSetup(CAPlacelessSetup, EventPlacelessSetup):

    def setUp(self, doctesttest=None):
        CAPlacelessSetup.setUp(self)
        EventPlacelessSetup.setUp(self)


class Context(object):

    def action(self, discriminator, callable, args=(), kw={}):
        self.last_action = (discriminator, callable, args, kw)

class I(Interface):

    name = TextLine()
    first = TextLine()
    last = TextLine()
    email = TextLine()
    address = TextLine()
    getfoo, setfoo = accessors(TextLine())
    extra1 = TextLine()
    extra2 = TextLine(required=False)

class C(object):

    implements(I)

    def __init__(self, *args, **kw):
        self.args = args
        self.kw = kw

    def getfoo(self): return self._foo
    def setfoo(self, v): self._foo = v

class V(object):
    name_widget = CustomWidgetFactory(Text)
    first_widget = CustomWidgetFactory(Text)
    last_widget = CustomWidgetFactory(Text)
    email_widget = CustomWidgetFactory(Text)
    address_widget = CustomWidgetFactory(Text)
    getfoo_widget = CustomWidgetFactory(Text)
    extra1_widget = CustomWidgetFactory(Text)
    extra2_widget = CustomWidgetFactory(Text)

class FooV(object):
    bar_widget = CustomWidgetFactory(Text)


class SampleData(object):

    name = u"foo"
    first = u"bar"
    last = u"baz"
    email = u"baz@dot.com"
    address = u"aa"
    getfoo = u"foo"
    extra1 = u"extra1"
    extra2 = u"extra2"

class Test(PlacelessSetup, unittest.TestCase):

    def setUp(self):
        self._context = Context()
        super(Test, self).setUp()
        ztapi.provideAdapter(IFoo, IBar, FooBarAdapter)

    def _invoke_add(self, schema=I, name="addthis", permission="zope.Public",
                    label="Add this", content_factory=C, class_=V,
                    arguments=['first', 'last'], keyword_arguments=['email'],
                    set_before_add=['getfoo'], set_after_add=['extra1'],
                    fields=None):
        """ Call the 'add' factory to process arguments into 'args'."""
        AddFormDirective(self._context,
                         schema=schema,
                         name=name,
                         permission=permission,
                         label=label,
                         content_factory=content_factory,
                         class_=class_,
                         arguments=arguments,
                         keyword_arguments=keyword_arguments,
                         set_before_add=set_before_add,
                         set_after_add=set_after_add,
                         fields=fields
                         )()

    def test_add_no_fields(self):
        _context = self._context
        self._invoke_add()
        result1 = _context.last_action
        self._invoke_add(
            fields="name first last email address getfoo extra1 extra2".split(),
            )
        result2 = _context.last_action

        self.assertEqual(result1, result2)

    def test_add_error_handling(self):
        # cannot use a field in arguments if it is not mentioned in fields
        self.assertRaises(ValueError, self._invoke_add,
                          fields="first email getfoo extra1".split())

        # cannot use a field in keyword_arguments if it is not
        # mentioned in fields

        self.assertRaises(ValueError, self._invoke_add,
                          fields="first last getfoo extra1".split())

        # cannot use a field in set_before_add if it is not mentioned in fields
        self.assertRaises(ValueError, self._invoke_add,
                          fields="first last email extra1".split())

        # cannot use a field in set_after_add if it is not mentioned in fields
        self.assertRaises(ValueError, self._invoke_add,
                          fields="first last email getfoo".split())

        # cannot use an optional field in arguments
        self.assertRaises(ValueError, self._invoke_add, arguments=["extra2"])

    def test_add(self, args=None):
        self._invoke_add()
        (descriminator, callable, args, kw) = self._context.last_action

        self.assertEqual(descriminator,
                         ('view', IAdding, 'addthis', IBrowserRequest, 
                         IDefaultBrowserLayer))

        self.assertEqual(callable, AddViewFactory)

        (name, schema, label, permission, layer, template,
         default_template, bases, for_, fields, content_factory,
         arguments, keyword_arguments, set_before_add,
         set_after_add)  = args

        self.assertEqual(name, 'addthis')
        self.assertEqual(schema, I)
        self.assertEqual(label, 'Add this')
        self.assertEqual(permission, CheckerPublic) # 'zope.Public' translated
        self.assertEqual(layer, IDefaultBrowserLayer)
        self.assertEqual(template, 'add.pt')
        self.assertEqual(default_template, 'add.pt')
        self.assertEqual(bases, (V, AddView, ))
        self.assertEqual(for_, IAdding)
        self.assertEqual(" ".join(fields),
                         "name first last email address getfoo extra1 extra2")
        self.assertEqual(content_factory, C)
        self.assertEqual(" ".join(arguments),
                         "first last")
        self.assertEqual(" ".join(keyword_arguments),
                         "email")
        self.assertEqual(" ".join(set_before_add),
                         "getfoo")
        self.assertEqual(" ".join(set_after_add),
                         "extra1 name address extra2")

        return args

    def test_add_content_factory_id(self, args=None):
        self._invoke_add(content_factory='C')
        (descriminator, callable, args, kw) = self._context.last_action

        self.assertEqual(descriminator,
                         ('view', IAdding, 'addthis', IBrowserRequest, 
                         IDefaultBrowserLayer))

        self.assertEqual(callable, AddViewFactory)

        (name, schema, label, permission, layer, template,
         default_template, bases, for_, fields, content_factory,
         arguments, keyword_arguments, set_before_add,
         set_after_add)  = args

        self.assertEqual(name, 'addthis')
        self.assertEqual(schema, I)
        self.assertEqual(label, 'Add this')
        self.assertEqual(permission, CheckerPublic) # 'zope.Public' translated
        self.assertEqual(layer, IDefaultBrowserLayer)
        self.assertEqual(template, 'add.pt')
        self.assertEqual(default_template, 'add.pt')
        self.assertEqual(bases, (V, AddView, ))
        self.assertEqual(for_, IAdding)
        self.assertEqual(" ".join(fields),
                         "name first last email address getfoo extra1 extra2")
        self.assertEqual(content_factory, 'C')
        self.assertEqual(" ".join(arguments),
                         "first last")
        self.assertEqual(" ".join(keyword_arguments),
                         "email")
        self.assertEqual(" ".join(set_before_add),
                         "getfoo")
        self.assertEqual(" ".join(set_after_add),
                         "extra1 name address extra2")

        return args

    def test_create(self):

        class Adding(object):

            implements(IAdding)

            def __init__(self, test):
                self.test = test

            def add(self, ob):
                self.ob = ob
                self.test.assertEqual(
                    ob.__dict__,
                    {'args': ("bar", "baz"),
                     'kw': {'email': 'baz@dot.com'},
                     '_foo': 'foo',
                    })
                return ob
            def nextURL(self):
                return "."

        adding = Adding(self)
        self._invoke_add()
        (descriminator, callable, args, kw) = self._context.last_action
        factory = AddViewFactory(*args)
        request = TestRequest()
        view = getMultiAdapter((adding, request), name='addthis')
        content = view.create('a',0,abc='def')

        self.failUnless(isinstance(content, C))
        self.assertEqual(content.args, ('a', 0))
        self.assertEqual(content.kw, {'abc':'def'})

    def test_create_content_factory_id(self):

        class Adding(object):

            implements(IAdding)

            def __init__(self, test):
                self.test = test

            def add(self, ob):
                self.ob = ob
                self.test.assertEqual(
                    ob.__dict__,
                    {'args': ("bar", "baz"),
                     'kw': {'email': 'baz@dot.com'},
                     '_foo': 'foo',
                    })
                return ob
            def nextURL(self):
                return "."

        # register content factory for content factory id lookup
        ztapi.provideAdapter(None, IComponentLookup, SiteManagerAdapter)
        ztapi.provideUtility(IFactory, Factory(C), name='C')
        
        adding = Adding(self)
        self._invoke_add(content_factory='C')
        (descriminator, callable, args, kw) = self._context.last_action
        factory = AddViewFactory(*args)
        request = TestRequest()
        view = getMultiAdapter((adding, request), name='addthis')
        content = view.create('a',0,abc='def')

        self.failUnless(isinstance(content, C))
        self.assertEqual(content.args, ('a', 0))
        self.assertEqual(content.kw, {'abc':'def'})

    def test_createAndAdd(self):

        class Adding(object):

            implements(IAdding)

            def __init__(self, test):
                self.test = test

            def add(self, ob):
                self.ob = ob
                self.test.assertEqual(
                    ob.__dict__,
                    {'args': ("bar", "baz"),
                     'kw': {'email': 'baz@dot.com'},
                     '_foo': 'foo',
                    })
                return ob
            def nextURL(self):
                return "."

        adding = Adding(self)
        self._invoke_add()
        (descriminator, callable, args, kw) = self._context.last_action
        factory = AddViewFactory(*args)
        request = TestRequest()
        view = getMultiAdapter((adding, request), name='addthis')

        view.createAndAdd(SampleData.__dict__)

        self.assertEqual(adding.ob.extra1, "extra1")
        self.assertEqual(adding.ob.extra2, "extra2")
        self.assertEqual(adding.ob.name, "foo")
        self.assertEqual(adding.ob.address, "aa")
        self.assertEqual(len(getEvents(IObjectCreatedEvent)), 1)
        self.assertEqual(len(getEvents(IObjectModifiedEvent)), 1)

    def test_createAndAdd_w_adapter(self):

        class Adding(object):

            implements(IAdding)

            def __init__(self, test):
                self.test = test

            def add(self, ob):
                self.ob = ob
                self.test.assertEqual(ob.__dict__, {'foo': 'bar'})
                return ob
            def nextURL(self):
                return "."

        adding = Adding(self)
        self._invoke_add(
            schema=IBar, name="addthis", permission="zope.Public",
            label="Add this", content_factory=Foo, class_=FooV,
            arguments=None, keyword_arguments=None,
            set_before_add=["bar"], set_after_add=None,
            fields=None
            )
        (descriminator, callable, args, kw) = self._context.last_action
        factory = AddViewFactory(*args)
        request = TestRequest()
        view = getMultiAdapter((adding, request), name='addthis')

        view.createAndAdd({'bar': 'bar'})

    def test_hooks(self):

        class Adding(object):
            implements(IAdding)

        adding = Adding()
        self._invoke_add()
        (descriminator, callable, args, kw) = self._context.last_action
        factory = AddViewFactory(*args)
        request = TestRequest()

        request.form.update(dict([
            ("field.%s" % k, v)
            for (k, v) in dict(SampleData.__dict__).items()
            ]))
        request.form[Update] = ''
        view = getMultiAdapter((adding, request), name='addthis')

        # Add hooks to V

        l=[None]

        def add(aself, ob):
            l[0] = ob
            self.assertEqual(
                ob.__dict__,
                {'args': ("bar", "baz"),
                 'kw': {'email': 'baz@dot.com'},
                 '_foo': 'foo',
                 })
            return ob

        V.add = add

        V.nextURL = lambda self: 'next'

        try:
            self.assertEqual(view.update(), '')

            self.assertEqual(view.errors, ())

            self.assertEqual(l[0].extra1, "extra1")
            self.assertEqual(l[0].extra2, "extra2")
            self.assertEqual(l[0].name, "foo")
            self.assertEqual(l[0].address, "aa")

            self.assertEqual(request.response.getHeader("Location"), "next")

            # Verify that calling update again doesn't do anything.
            l[0] = None
            self.assertEqual(view.update(), '')
            self.assertEqual(l[0], None)

        finally:
            # Uninstall hooks
            del V.add
            del V.nextURL


def test_suite():
    return unittest.makeSuite(Test)

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