示例#1
0
class TestModel(unittest.TestCase):
    def setUpClass():
        logging.basicConfig(level=logging.DEBUG)

    def setUp(self):
        self.logger = logging.getLogger()

        self.testFiles = []

        self.itemFile = Datafile('test.model.items.xml', self.id())
        self.itemFileAuctionOnly = Datafile('test.model.items.auction_only.xml', self.id())
        self.sessionFile = Datafile('test.model.session.xml', self.id())
        self.currencyFile = Datafile('test.model.currency.xml', self.id())
        self.importFileCsv = Datafile('test.model.import.csv', self.id())
        self.importFileTxt = Datafile('test.model.import.txt', self.id())

        self.dataset = Dataset(
                self.logger, './',
                self.sessionFile.getFilename(),
                self.itemFile.getFilename(),
                self.currencyFile.getFilename())
        self.dataset.restore()

        self.currency = Currency(
                self.logger,
                self.dataset,
                currencyCodes=['czk', 'eur'])
        self.model = Model(
                self.logger,
                self.dataset,
                self.currency)

    def tearDown(self):
        self.itemFile.clear()
        self.sessionFile.clear()
        self.currencyFile.clear()
        self.importFileCsv.clear()
        self.importFileTxt.clear()
        for file in self.testFiles:
            file.clear()

        del self.model
        del self.currency
        del self.dataset

    def restoreTestFile(self, filename):
        testFile = Datafile(filename, self.id())
        self.testFiles.append(testFile)
        return testFile
        
    def test_getItem(self):
        item = self.model.getItem('A2')
        self.assertDictContainsSubset({ItemField.CODE: 'A2'}, item)
        self.assertListEqual([Decimal('250'), Decimal('9.21')], [currency[CurrencyField.AMOUNT] for currency in item[ItemField.INITIAL_AMOUNT_IN_CURRENCY]])
        self.assertListEqual([Decimal('300'), Decimal('11.06')], [currency[CurrencyField.AMOUNT] for currency in item[ItemField.AMOUNT_IN_CURRENCY]])
        self.assertListEqual([], [currency[CurrencyField.AMOUNT] for currency in item[ItemField.AMOUNT_IN_AUCTION_IN_CURRENCY]])
    
    def test_addNewItem(self):
        sessionID = 11111

        # add (on show)
        self.assertEqual(
                self.model.addNewItem(sessionID, 23, 'Mysteria', 'Wolf', 'Pastel', None, None, None),
                Result.SUCCESS)
        addedItem = self.dataset.getItems('Owner=="23" and Title=="Mysteria" and Author=="Wolf"')[0]
        self.assertDictContainsSubset({
                        ItemField.STATE: ItemState.ON_SHOW,
                        ItemField.MEDIUM: 'Pastel',
                        ItemField.NOTE: None},
                addedItem);

        # duplicate add
        self.assertEqual(
                self.model.addNewItem(sessionID, 23, 'Mysteria', 'Wolf', None, None, None, None),
                Result.DUPLICATE_ITEM)

        # add (on sale) (amount/charity is converted but search expression assumes strings)
        self.assertEqual(
                self.model.addNewItem(sessionID, 35, 'Mysteria', 'Tiger', '', 123.5, 10, 'Good Stuff'),
                Result.SUCCESS)
        addedItem = self.dataset.getItems('Owner=="35" and Title=="Mysteria" and Author=="Tiger" and Charity=="10" and InitialAmount=="123.5"')[0]
        self.assertDictContainsSubset({
                        ItemField.INITIAL_AMOUNT: 123.5,
                        ItemField.CHARITY: 10,
                        ItemField.STATE: ItemState.ON_SALE,
                        ItemField.MEDIUM: None,
                        ItemField.NOTE: 'Good Stuff'},
                addedItem);

        # add (quotes)
        self.assertEqual(
                self.model.addNewItem(sessionID, 98, 'Quotted"Title', 'Qu"es', 'Photo', None, None, 'Do not touch.'),
                Result.SUCCESS)

        # add (empty parameters)
        self.assertEqual(
                self.model.addNewItem(sessionID, 99, 'Strong', 'Lemur', None, None, None, ''),
                Result.SUCCESS)
        addedItem = self.dataset.getItems('Owner=="99" and Title=="Strong" and Author=="Lemur"')[0]
        self.assertDictContainsSubset({
                        ItemField.MEDIUM: None,
                        ItemField.NOTE: None},
                addedItem);

        # add item from an import
        importNumber = 100
        self.assertEqual(
                self.model.addNewItem(sessionID, 99, 'Shy', 'Lemur', None, None, None, '', importNumber),
                Result.SUCCESS)
        addedItem = self.dataset.getItems('Owner=="99" and Title=="Shy" and Author=="Lemur"')[0]
        self.assertDictContainsSubset({
                        ItemField.CODE: str(importNumber),
                        ItemField.IMPORT_NUMBER: importNumber},
                addedItem);

        # add updated item (differs in amount/charity)
        self.assertEqual(
                self.model.addNewItem(sessionID, 99, 'Shy', 'Lemur', None, '12.5', 100, 'Some note', importNumber),
                Result.DUPLICATE_IMPORT_NUMBER)

        # add updated item (differs in name)
        self.assertEqual(
                self.model.addNewItem(sessionID, 99, 'Smiling', 'Lemur', None, None, None, 'Some note', importNumber),
                Result.DUPLICATE_IMPORT_NUMBER)

        # add item from an import with a and import number that matches an existing code
        importNumber = 3
        self.assertEqual(len(self.dataset.getItems('Code=="{0}"'.format(importNumber))), 0)
        self.assertEqual(
                self.model.addNewItem(sessionID, 99, 'Funny', 'Cat', None, None, None, '', importNumber),
                Result.SUCCESS_BUT_IMPORT_RENUMBERED)
        addedItem = self.dataset.getItems('Owner=="99" and Title=="Funny" and Author=="Cat"')[0]
        self.assertDictContainsSubset({
                        ItemField.IMPORT_NUMBER: importNumber},
                addedItem);

        # added list
        addedItemCodes = self.model.getAdded(sessionID)
        self.assertEqual(len(addedItemCodes), 6);


    def test_getAddedItems(self):
        sessionID = 11111

        # add items
        self.assertEqual(self.model.addNewItem(sessionID, 23, 'Mysteria', 'Wolf', 'Oil', None, None, None), Result.SUCCESS)
        self.assertEqual(self.model.addNewItem(sessionID, 35, 'Mysteria', 'Tiger', 'Pencil', '123', '10', None), Result.SUCCESS)

        # get added items
        addedItems = self.model.getAddedItems(sessionID)

        self.assertEqual(len(addedItems), 2);
        item = [item for item in addedItems if item[ItemField.OWNER] == 23][0]
        self.assertListEqual([], [currencyAmount[CurrencyField.AMOUNT] for currencyAmount in item[ItemField.INITIAL_AMOUNT_IN_CURRENCY]])
        item = [item for item in addedItems if item[ItemField.OWNER] == 35][0]
        self.assertListEqual(
                [Decimal('123'), Decimal('4.53')],
                [currencyAmount[CurrencyField.AMOUNT] for currencyAmount in item[ItemField.INITIAL_AMOUNT_IN_CURRENCY]])


    def test_updateItem(self):
        # update item
        self.assertEqual(
                self.model.updateItem(56,
                    owner=1, title='Wolf', author='Greenwolf', medium='Color Pencils', state=ItemState.ON_SALE, 
                    initialAmount='105', charity='50', amount=None, buyer=None, note=None),
                Result.SUCCESS)
        updatedItem = self.dataset.getItems('Owner=="1" and Title=="Wolf" and Author=="Greenwolf" and Medium=="Color Pencils"')[0]
        self.assertDictContainsSubset({
                        ItemField.STATE: ItemState.ON_SALE,
                        ItemField.INITIAL_AMOUNT: 105,
                        ItemField.CHARITY: 50,
                        ItemField.AMOUNT: None,
                        ItemField.NOTE: None},
                updatedItem);        
        self.assertIsNone(updatedItem[ItemField.AMOUNT]);
        self.assertIsNone(updatedItem[ItemField.BUYER]);

        # update item (range error of charity)
        self.assertEqual(
                self.model.updateItem(56,
                    owner=1, title='Wolf', author='Greenwolf', medium='Color Pencils', state=ItemState.FINISHED,
                    initialAmount='105', charity='150', amount='200', buyer='20', note=None),
                Result.INVALID_VALUE)

        # update item (consistency error)
        self.assertEqual(
                self.model.updateItem(56,
                    owner=1, title='Wolf', author='Greenwolf', medium='Color Pencils', state=ItemState.FINISHED,
                    initialAmount='105', charity='10', amount=None, buyer=None, note=None),
                Result.AMOUNT_NOT_DEFINED)

    def test_deleteItems(self):
        # 1. Delete item
        self.assertEqual(self.model.deleteItems(['A11', 'A2', 'A999']), 2)
        self.assertIsNone(self.model.getItem('A11'));
        self.assertIsNone(self.model.getItem('A2'));
        self.assertIsNone(self.model.getItem('A999'));

    def test_getItemNetAmount(self):
        item = self.model.getItem('A2')
        amountNet, amountCharity = self.model.getItemNetAmount(item)
        self.assertEqual(amountNet, Decimal('270'))
        self.assertEqual(amountCharity, Decimal('30'))
        
    def test_getPotentialCharityAmount(self):
        charityAmount = self.model.getPotentialCharityAmount()
        self.assertEqual(charityAmount, Decimal('299'))

    def test_getBadgeReconciliationSummary(self):
        # Owner that has no delivered item
        self.logger.info('Badge 1')
        summary = self.model.getBadgeReconciliationSummary(1)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('0'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('350'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('350'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 2)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 2)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 2)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 0)

        # Owner that has just delivered items
        self.logger.info('Badge 2')
        summary = self.model.getBadgeReconciliationSummary(2)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('447'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('49'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('-398'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 3)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 2)

        # Owner that has delivered items and bought items
        self.logger.info('Badge 4')
        summary = self.model.getBadgeReconciliationSummary(4)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('235'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('36'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('57'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('-142'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 1)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 2)

        # Owner that has items either finished, not delivered, or unsold
        self.logger.info('Badge 6')
        summary = self.model.getBadgeReconciliationSummary(6)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('0'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('0'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 1)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 0)

        # Buyer that has just bought items and some of the bought items are finished
        self.logger.info('Badge 11')
        summary = self.model.getBadgeReconciliationSummary(11)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('0'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('429'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('429'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 3)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 0)

        # Buyer that has items either in auction or finished
        self.logger.info('Badge 12')
        summary = self.model.getBadgeReconciliationSummary(12)
        self.assertEqual(summary[SummaryField.GROSS_SALE_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.CHARITY_DEDUCTION], Decimal('0'))
        self.assertEqual(summary[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('0'))
        self.assertEqual(summary[SummaryField.TOTAL_DUE_AMOUNT], Decimal('0'))
        self.assertEqual(len(summary[SummaryField.AVAILABLE_UNSOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.AVAILABLE_BOUGHT_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.PENDING_SOLD_ITEMS]), 0)
        self.assertEqual(len(summary[SummaryField.DELIVERED_SOLD_ITEMS]), 0)

    def test_reconciliateBadge(self):
        # Badge 1 contains:
        # * sold item which has not been paid for (code: A2)
        # * self-sale of an item (code: 56)
        summaryBefore = self.model.getBadgeReconciliationSummary(1)
        self.assertTrue(self.model.reconciliateBadge(1))
        summaryAfter = self.model.getBadgeReconciliationSummary(1)
        self.assertEqual(summaryAfter[SummaryField.GROSS_SALE_AMOUNT], Decimal('200'))
        self.assertEqual(summaryAfter[SummaryField.CHARITY_DEDUCTION], Decimal('20'))
        self.assertEqual(summaryAfter[SummaryField.BOUGHT_ITEMS_AMOUNT], Decimal('0'))
        self.assertEqual(summaryAfter[SummaryField.TOTAL_DUE_AMOUNT], Decimal('-180'))
        self.assertListEqual(
            [],
            summaryAfter[SummaryField.AVAILABLE_UNSOLD_ITEMS])
        self.assertListEqual(
            [],
            summaryAfter[SummaryField.AVAILABLE_BOUGHT_ITEMS])
        self.assertListEqual(
            ['A2'],
            [item[ItemField.CODE] for item in summaryAfter[SummaryField.PENDING_SOLD_ITEMS]])
        self.assertListEqual(
            ['56'],
            [item[ItemField.CODE] for item in summaryAfter[SummaryField.DELIVERED_SOLD_ITEMS]])
    
        for itemUnsoldBefore in summaryBefore[SummaryField.AVAILABLE_UNSOLD_ITEMS]:
            self.assertEqual(
                    self.model.getItem(itemUnsoldBefore[ItemField.CODE])[ItemField.STATE],
                    ItemState.FINISHED,
                    'Item {0}'.format(itemUnsoldBefore[ItemField.CODE]))

        for itemBoughtBefore in summaryBefore[SummaryField.AVAILABLE_BOUGHT_ITEMS]:
            self.assertEqual(
                    self.model.getItem(itemBoughtBefore[ItemField.CODE])[ItemField.STATE],
                    ItemState.DELIVERED,
                    'Item {0}'.format(itemBoughtBefore[ItemField.CODE]))

        for itemDeliveredBefore in summaryBefore[SummaryField.DELIVERED_SOLD_ITEMS]:
            self.assertEqual(
                    self.model.getItem(itemDeliveredBefore[ItemField.CODE])[ItemField.STATE],
                    ItemState.FINISHED,
                    'Item {0}'.format(itemDeliveredBefore[ItemField.CODE]))


    def test_summaryChecksum(self):
        summaryA = self.model.getBadgeReconciliationSummary(1)
        summaryB = self.model.getBadgeReconciliationSummary(11)
        self.assertNotEqual(Summary.calculateChecksum(summaryA), Summary.calculateChecksum(summaryB))

    def test_getCashDrawerSummary(self):
        summary = self.model.getCashDrawerSummary()
        self.assertIsNotNone(summary)
        self.assertEqual(summary[DrawerSummaryField.TOTAL_GROSS_CASH_DRAWER_AMOUNT], Decimal('709'))
        self.assertEqual(summary[DrawerSummaryField.TOTAL_NET_CHARITY_AMOUNT], Decimal('112'))
        self.assertEqual(summary[DrawerSummaryField.TOTAL_NET_AVAILABLE_AMOUNT], Decimal('597'))
        self.assertListEqual(
                sorted([actorSummary.Badge for actorSummary in summary[DrawerSummaryField.BUYERS_TO_BE_CLEARED]]),
                [1, 3, 4, 11, 13])
        self.assertListEqual(
                sorted([actorSummary.Badge for actorSummary in summary[DrawerSummaryField.OWNERS_TO_BE_CLEARED]]),
                [1, 2, 3, 4, 6, 7])
        self.assertEqual(len(summary[DrawerSummaryField.PENDING_ITEMS]), 3)


    def test_importItemsFromCsv(self):
        # 1. Import
        sessionID = 11111
        binaryStream = io.open(self.importFileCsv.getFilename(), mode='rb')
        importedItems, importedChecksum = self.model.importCSVFile(sessionID, binaryStream)
        binaryStream.close()

        # 2. Verify
        self.assertEqual(len(importedItems), 13)
        self.assertEqual(importedItems[0][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[1][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[2][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[3][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[4][ImportedItemField.IMPORT_RESULT], Result.INVALID_CHARITY)
        self.assertEqual(importedItems[5][ImportedItemField.IMPORT_RESULT], Result.INCOMPLETE_SALE_INFO)
        self.assertEqual(importedItems[6][ImportedItemField.IMPORT_RESULT], Result.INVALID_AMOUNT)
        self.assertEqual(importedItems[7][ImportedItemField.IMPORT_RESULT], Result.INVALID_AUTHOR)
        self.assertEqual(importedItems[8][ImportedItemField.IMPORT_RESULT], Result.INVALID_TITLE)
        self.assertEqual(importedItems[9][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[10][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[11][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[12][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)

        # 3. Apply
        defaultOwner = 2
        result, skippedItems, renumberedItems = self.model.applyImport(sessionID, importedChecksum, defaultOwner)
        self.assertEqual(result, Result.SUCCESS)
        self.assertEqual(len(self.model.getAdded(sessionID)), 6)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Smooth \\\"Frog\\\"" and Author=="Greentiger" and State=="{1}" and InitialAmount=="120" and Charity=="47"'.format(
                        defaultOwner, ItemState.ON_SALE))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Draft Horse" and Author=="Greentiger" and State=="{1}" and InitialAmount=="500" and Charity=="0"'.format(
                        defaultOwner, ItemState.ON_SALE))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Žluťoučký kůň" and Author=="Greentiger" and State=="{1}"'.format(
                        defaultOwner, ItemState.ON_SHOW))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Eastern Dragon" and Author=="Redwolf" and State=="{1}"'.format(
                        defaultOwner, ItemState.SOLD))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="7" and Title=="More Wolves" and Author=="Greenfox" and State=="{0}" and InitialAmount=="280" and Charity=="50"'.format(
                        ItemState.ON_SALE))), 1)

        # 4. Re-apply
        result, skippedItems, renumberedItems = self.model.applyImport(sessionID, importedChecksum, defaultOwner)
        self.assertEqual(result, Result.NO_IMPORT)

        # 5. Re-apply with invalid checksum
        binaryStream = io.open(self.importFileCsv.getFilename(), mode='rb')
        importedItems, importedChecksum = self.model.importCSVFile(sessionID, binaryStream)
        binaryStream.close()
        result, skippedItems, renumberedItems = self.model.applyImport(sessionID, importedChecksum + 50, defaultOwner)
        self.assertEqual(result, Result.INVALID_CHECKSUM)

    def test_importItemsFromCsv_ImportNumberReuse(self):
        # Verify next code. This is crucial for the last test.
        NEXT_AVAILABLE_CODE = 57

        # 1. Import
        importFile = self.restoreTestFile('test.model.import_number.csv');
        sessionID = 11111
        binaryStream = io.open(importFile.getFilename(), mode='rb')
        importedItems, importedChecksum = self.model.importCSVFile(sessionID, binaryStream)
        binaryStream.close()

        # 2. Verify
        self.assertEqual(len(importedItems), 11)
        self.assertEqual(importedItems[0][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[1][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[2][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[3][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[4][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[5][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[6][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[7][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[8][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[9][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[10][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)

        # 3. Apply
        defaultOwner = 2
        result, skippedItems, renumberedItems = self.model.applyImport(sessionID, importedChecksum, defaultOwner)
        self.assertEqual(Result.SUCCESS, result)
        self.assertEqual(7, len(self.model.getAdded(sessionID)))

        # 3a. Check that if Import Number is missing, it will be left empty.
        self.assertDictContainsSubset(
                { ItemField.IMPORT_NUMBER: None },
                self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Moon cycles"')[0])

        # 3b. Check that a duplicate Import Number in the import was not imported.
        self.assertEqual(
                0,
                len(self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Fullmoon"')))
        self.assertEqual(
                0,
                len(self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="No moon"')))

        # 3c. Check that an existing item with a matching Import Number has not been updated
        # in case there were changes.
        self.assertEqual(
                1,
                len(self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Half moon"')))
        updatedItem = self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Half moon"')[0]
        self.assertIn(updatedItem[ItemField.CODE], self.model.getAdded(sessionID))

        # 3d. Check that an existing item with a matching Import Number has not been updated.
        nonupdatedItem = self.dataset.getItems('Owner=="7" and Author=="Greenfox" and Title=="White Snow"')[0]
        self.assertNotIn(nonupdatedItem[ItemField.CODE], self.model.getAdded(sessionID))

        # 3e. Check that item which import number might have been used earlier is renumbered.
        renumberedItem = self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Day phases"')[0]
        self.assertNotEqual('45', renumberedItem[ItemField.CODE]);
        self.assertEqual(45, renumberedItem[ItemField.IMPORT_NUMBER]);

        # 3f. Check that Import Number is used case Code if the Code might not have been used previously.
        self.assertDictContainsSubset(
                { ItemField.CODE: '80', ItemField.IMPORT_NUMBER: 80 },
                self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Morning"')[0])

        self.assertDictContainsSubset(
                { ItemField.CODE: '90', ItemField.IMPORT_NUMBER: 90 },
                self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Afternoon"')[0])

        # 3g. Check that order of occurance in the import has no impact on ability to use Import Number as Code
        self.assertDictContainsSubset(
                { ItemField.CODE: '85', ItemField.IMPORT_NUMBER: 85 },
                self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Noon"')[0])

        # 3f. Check that if Import Number might be used as Code at the start of the import, it will be used as Code.
        # No unnumbered or re-numbered item will prevent that.
        self.assertDictContainsSubset(
                { ItemField.CODE: str(NEXT_AVAILABLE_CODE), ItemField.IMPORT_NUMBER: NEXT_AVAILABLE_CODE },
                self.dataset.getItems('Owner=="7" and Author=="Redpanda" and Title=="Day"')[0])


    def test_importItemsFromText(self):
        textStream = io.open(self.importFileTxt.getFilename(), mode='rt', encoding='utf-8')
        text = '\n'.join(textStream.readlines())
        textStream.close()

        # 1. Import
        sessionID = 11111
        importedItems, importedChecksum = self.model.importText(sessionID, text)

        # 2. Verify
        self.assertEqual(len(importedItems), 10)
        self.assertEqual(importedItems[0][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[1][ImportedItemField.IMPORT_RESULT], Result.SUCCESS)
        self.assertEqual(importedItems[2][ImportedItemField.IMPORT_RESULT], Result.INVALID_CHARITY)
        self.assertEqual(importedItems[3][ImportedItemField.IMPORT_RESULT], Result.INCOMPLETE_SALE_INFO)
        self.assertEqual(importedItems[4][ImportedItemField.IMPORT_RESULT], Result.INVALID_AMOUNT)
        self.assertEqual(importedItems[5][ImportedItemField.IMPORT_RESULT], Result.INVALID_AUTHOR)
        self.assertEqual(importedItems[6][ImportedItemField.IMPORT_RESULT], Result.INVALID_TITLE)
        self.assertEqual(importedItems[7][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[8][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)
        self.assertEqual(importedItems[9][ImportedItemField.IMPORT_RESULT], Result.DUPLICATE_ITEM)

        # 3. Apply
        owner = 2
        result, skippedItems, renumberedItems = self.model.applyImport(sessionID, importedChecksum, owner)
        self.assertEqual(result, Result.SUCCESS)
        self.assertEqual(len(self.model.getAdded(sessionID)), 2)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Smooth Frog" and Author=="Greentiger" and State=="{1}" and InitialAmount=="120" and Charity=="47"'.format(
                        owner, ItemState.ON_SALE))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Žluťoučký kůň" and Author=="Greentiger" and State=="{1}"'.format(
                        owner, ItemState.ON_SHOW))), 1)
        self.assertEqual(len(self.dataset.getItems(
                'Owner=="{0}" and Title=="Eastern Dragon" and Author=="Redwolf" and State=="{1}"'.format(
                        owner, ItemState.SOLD))), 1)

    def test_getNetAmount(self):
        # Regular amount
        grossAmount = 253
        saleAmount, charityAmount = self.model.getNetAmount(Decimal(grossAmount), 47)
        self.assertEqual((saleAmount, charityAmount), (134, 119))
        self.assertEqual(saleAmount + charityAmount, grossAmount)

        # Excessive amount
        self.assertEqual(self.model.getNetAmount(Decimal('1E+34'), 14), (0, 0))

        # Invalid amount
        self.assertEqual(self.model.getNetAmount(None, 23), (0, 0))

    def test_getSendItemToAuction(self):
        # Item of acceptable state (AUCT)
        item = self.model.sendItemToAuction('A10')
        self.assertIsNotNone(item)
        self.assertDictContainsSubset(
                {
                        ItemField.CODE:'A10',
                        ItemField.AMOUNT_IN_AUCTION:item[ItemField.AMOUNT]},
                self.model.getItemInAuction())
        self.model.clearAuction()
        self.assertIsNone(self.model.getItemInAuction())

        # Item of invalid state (SOLD)
        self.assertIsNone(self.model.sendItemToAuction('A13'))
        self.assertIsNone(self.model.getItemInAuction())

    def test_closeItemAsNotSold(self):
        # Close item
        self.assertEqual(Result.SUCCESS, self.model.closeItemAsNotSold('55'))
        item = self.model.getItem('55')
        self.assertDictContainsSubset(
                {
                        ItemField.STATE: ItemState.NOT_SOLD,
                        ItemField.BUYER: None,
                        ItemField.AMOUNT: None},
                item)

        # Close item which is not closable
        self.assertEqual(Result.ITEM_NOT_CLOSABLE, self.model.closeItemAsNotSold('A13'))

    def test_closeItemAsSold(self):
        # Close item
        self.assertEqual(Result.SUCCESS, self.model.closeItemAsSold('55', Decimal(1000), 9999))
        item = self.dataset.getItems('Buyer=="{0}"'.format(9999))[0]
        self.assertDictContainsSubset(
                {
                        ItemField.STATE: ItemState.SOLD,
                        ItemField.BUYER: 9999,
                        ItemField.AMOUNT: Decimal(1000)},
                item)

        # Close item which is not closable
        self.assertEqual(Result.ITEM_NOT_CLOSABLE, self.model.closeItemAsSold('A13', Decimal(1000), 9999))

    def test_closeItemIntoAuction(self):
        # Close item
        self.assertEqual(Result.SUCCESS, self.model.closeItemIntoAuction('55', Decimal(1000), 9999, None))
        item = self.dataset.getItems('Buyer=="{0}"'.format(9999))[0]
        self.assertDictContainsSubset(
                {
                        ItemField.STATE: ItemState.IN_AUCTION,
                        ItemField.BUYER: 9999,
                        ItemField.AMOUNT: Decimal(1000)},
                item)

        # Close item which is not closable
        self.assertEqual(Result.ITEM_NOT_CLOSABLE, self.model.closeItemIntoAuction('A13', Decimal(1000), 9999, None))

    def test_getAllItemsInAuction(self):
        auctionItems = self.model.getAllItemsInAuction()
        self.assertListEqual(
                ['A9', 'A10'],
                [item[ItemField.CODE] for item in auctionItems]);

    def test_getAllItemsInAuction_Ordering(self):
        datasetAuction = Dataset(
                self.logger, './',
                self.sessionFile.getFilename(),
                self.itemFileAuctionOnly.getFilename(),
                self.currencyFile.getFilename())
        datasetAuction.restore()
        modelAuction = Model(
                self.logger,
                datasetAuction,
                self.currency)

        auctionItems = modelAuction.getAllItemsInAuction()
        auctionItems.sort(key=lambda item: item[ItemField.AUCTION_SORT_CODE])

        for item in auctionItems:
            print('{0} - {1}'.format(item[ItemField.AUTHOR], item[ItemField.AMOUNT]))

        # Check that there is no block authors larger than two
        largestBlockSize = 0
        largestBlockAuthor = None
        blockAuthor = None
        blockSize = 0
        for item in auctionItems:
            if blockAuthor is not None and item[ItemField.AUTHOR] == blockAuthor:
                blockSize = blockSize + 1
            else:
                if blockSize > largestBlockSize:
                    largestBlockSize = blockSize
                    largestBlockAuthor = blockAuthor
                blockAuthor = item[ItemField.AUTHOR]
                blockSize = 1
        self.assertGreaterEqual(2, largestBlockSize, 'Author: ' + str(largestBlockAuthor))


    def test_generateDeviceCode(self):
        adminSessionID = self.model.startNewSession(UserGroups.ADMIN, '127.0.0.1')

        sessionID = self.model.startNewSession(UserGroups.UNKNOWN, '192.168.0.1')

        # If multiple numbers are generated per session, only the last one is valid
        deviceCode1 = self.model.generateDeviceCode(sessionID)
        self.assertIsNotNone(deviceCode1)
        deviceCode2 = self.model.generateDeviceCode(sessionID)
        self.assertIsNotNone(deviceCode2)
        self.assertEqual(Result.DISABLED_DEVICE_CODE, self.model.approveDeviceCode(adminSessionID, deviceCode1, UserGroups.SCAN_DEVICE))
        self.assertEqual(Result.SUCCESS, self.model.approveDeviceCode(adminSessionID, deviceCode2, UserGroups.SCAN_DEVICE))


    def test_getSessionUserGroup(self):
        adminSessionID = self.model.startNewSession(UserGroups.ADMIN, '127.0.0.1')

        sessionID = self.model.startNewSession(UserGroups.UNKNOWN, '192.168.0.1')
        self.assertIsNotNone(sessionID)

        # User group is defined in session
        self.assertEqual(UserGroups.ADMIN, self.model.getSessionUserGroup(adminSessionID))
        self.assertEqual(UserGroups.UNKNOWN, self.model.getSessionUserGroup(sessionID))

        # If a device code is approved, associated user group is used.
        deviceCode = self.model.generateDeviceCode(sessionID)
        self.assertEqual(Result.SUCCESS, self.model.approveDeviceCode(adminSessionID, deviceCode, UserGroups.SCAN_DEVICE))
        self.assertEqual(UserGroups.SCAN_DEVICE, self.model.getSessionUserGroup(sessionID))

        # If a device code is dropped, user group is UNKNOWN.
        self.model.dropDeviceCode(adminSessionID, deviceCode)
        self.assertEqual(UserGroups.UNKNOWN, self.model.getSessionUserGroup(sessionID))