source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/tests/test_batching.py @ 12256

Last change on this file since 12256 was 12256, checked in by Henrik Bettermann, 10 years ago

Change document_id generation algorithm. Use Universally Unique IDentifiers instead of consecutive numbers.

  • Property svn:keywords set to Id
File size: 23.0 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_batching.py 12256 2014-12-18 12:58:12Z henrik $
3##
4## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19"""Unit tests for customer-related data processors.
20"""
21import os
22import shutil
23import tempfile
24import unittest
25import datetime
26import grok
27from time import time
28from zope.event import notify
29from zope.component import createObject, queryUtility
30from zope.component.hooks import setSite, clearSite
31from zope.catalog.interfaces import ICatalog
32from zope.interface.verify import verifyClass, verifyObject
33from hurry.workflow.interfaces import IWorkflowState
34
35from waeup.ikoba.app import Company
36from waeup.ikoba.interfaces import (
37    IBatchProcessor, FatalCSVError, IUserAccount, VERIFIED)
38from waeup.ikoba.customers.batching import (
39    CustomerProcessor, CustomerDocumentProcessor, ContractProcessor)
40from waeup.ikoba.customers.customer import Customer
41from waeup.ikoba.customers.documents import CustomerSampleDocument
42from waeup.ikoba.testing import FunctionalLayer, FunctionalTestCase
43
44CUSTOMER_SAMPLE_DATA = open(
45    os.path.join(os.path.dirname(__file__), 'sample_customer_data.csv'),
46    'rb').read()
47
48CUSTOMER_HEADER_FIELDS = CUSTOMER_SAMPLE_DATA.split(
49    '\n')[0].split(',')
50
51CUSTOMER_SAMPLE_DATA_UPDATE = open(
52    os.path.join(os.path.dirname(__file__), 'sample_customer_data_update.csv'),
53    'rb').read()
54
55CUSTOMER_HEADER_FIELDS_UPDATE = CUSTOMER_SAMPLE_DATA_UPDATE.split(
56    '\n')[0].split(',')
57
58CUSTOMER_SAMPLE_DATA_DUPLICATES = open(
59    os.path.join(os.path.dirname(__file__),
60                 'sample_customer_data_duplicates.csv'),
61    'rb').read()
62
63CUSTOMER_HEADER_FIELDS_DUPLICATES = CUSTOMER_SAMPLE_DATA_DUPLICATES.split(
64    '\n')[0].split(',')
65
66DOCUMENT_SAMPLE_DATA = open(
67    os.path.join(os.path.dirname(__file__), 'sample_document_data.csv'),
68    'rb').read()
69
70DOCUMENT_HEADER_FIELDS = DOCUMENT_SAMPLE_DATA.split(
71    '\n')[0].split(',')
72
73CONTRACT_SAMPLE_DATA = open(
74    os.path.join(os.path.dirname(__file__), 'sample_contract_data.csv'),
75    'rb').read()
76
77CONTRACT_HEADER_FIELDS = CONTRACT_SAMPLE_DATA.split(
78    '\n')[0].split(',')
79
80class CustomerImportExportSetup(FunctionalTestCase):
81
82    layer = FunctionalLayer
83
84    def setUp(self):
85        super(CustomerImportExportSetup, self).setUp()
86        self.dc_root = tempfile.mkdtemp()
87        self.workdir = tempfile.mkdtemp()
88        app = Company()
89        app['datacenter'].setStoragePath(self.dc_root)
90        self.getRootFolder()['app'] = app
91        self.app = self.getRootFolder()['app']
92        setSite(app)
93        self.logfile = os.path.join(
94            self.app['datacenter'].storage, 'logs', 'customers.log')
95
96        #Add some products
97        self.product = createObject('waeup.Product')
98        self.product.product_id = u'SAM'
99        self.product.title = u'Our Samle Product'
100        self.product.contract_category = u'sample'
101        self.app['products'].addProduct(self.product)
102        return
103
104    def tearDown(self):
105        super(CustomerImportExportSetup, self).tearDown()
106        shutil.rmtree(self.workdir)
107        shutil.rmtree(self.dc_root)
108        clearSite()
109        return
110
111    def setup_for_export(self):
112        customer = Customer()
113        customer.customer_id = u'A111111'
114        self.app['customers'][customer.customer_id] = self.customer = customer
115        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
116        return
117
118    def setup_customer(self, customer):
119        customer.matric_number = u'234'
120        customer.perm_address = u'Customerroad 21\nLagos 123456\n'
121        customer.reg_number = u'123'
122        customer.firstname = u'Anna'
123        customer.lastname = u'Tester'
124        customer.middlename = u'M.'
125        customer.date_of_birth = datetime.date(1981, 2, 4)
126        #customer.sex = 'f'
127        customer.email = 'anna@sample.com'
128        customer.phone = u'+234-123-12345'
129        customer.notice = u'Some notice\nin lines.'
130        customer.nationality = u'NG'
131        # Add document
132        document = createObject('waeup.CustomerSampleDocument')
133        document.title = u'My Document'
134        document.document_id = u'DOC1'
135        customer['documents'].addDocument(document)
136        IWorkflowState(document).setState(VERIFIED)
137        self.document = document
138        # Add contract
139        contract = createObject('waeup.SampleContract')
140        contract.title = u'My Contract'
141        contract.product_object = self.product
142        contract.document_object = self.document
143        customer['contracts'].addContract(contract)
144        self.contract = contract
145        return customer
146
147class CustomerProcessorTest(CustomerImportExportSetup):
148
149    layer = FunctionalLayer
150
151    def setUp(self):
152        super(CustomerProcessorTest, self).setUp()
153
154        # Add customer
155        customer = Customer()
156        self.app['customers'].addCustomer(customer)
157        customer = self.setup_customer(customer)
158        notify(grok.ObjectModifiedEvent(customer))
159        self.customer = self.app['customers'][customer.customer_id]
160
161        self.processor = CustomerProcessor()
162        self.csv_file = os.path.join(self.workdir, 'sample_customer_data.csv')
163        self.csv_file_update = os.path.join(
164            self.workdir, 'sample_customer_data_update.csv')
165        self.csv_file_duplicates = os.path.join(
166            self.workdir, 'sample_customer_data_duplicates.csv')
167        open(self.csv_file, 'wb').write(CUSTOMER_SAMPLE_DATA)
168        open(self.csv_file_update, 'wb').write(CUSTOMER_SAMPLE_DATA_UPDATE)
169        open(self.csv_file_duplicates, 'wb').write(CUSTOMER_SAMPLE_DATA_DUPLICATES)
170
171    def test_interface(self):
172        # Make sure we fulfill the interface contracts.
173        assert verifyObject(IBatchProcessor, self.processor) is True
174        assert verifyClass(
175            IBatchProcessor, CustomerProcessor) is True
176
177    def test_parentsExist(self):
178        self.assertFalse(self.processor.parentsExist(None, dict()))
179        self.assertTrue(self.processor.parentsExist(None, self.app))
180
181    def test_entryExists(self):
182        assert self.processor.entryExists(
183            dict(customer_id='ID_NONE'), self.app) is False
184        assert self.processor.entryExists(
185            dict(reg_number='123'), self.app) is True
186
187    def test_getParent(self):
188        parent = self.processor.getParent(None, self.app)
189        assert parent is self.app['customers']
190
191    def test_getEntry(self):
192        assert self.processor.getEntry(
193            dict(customer_id='ID_NONE'), self.app) is None
194        assert self.processor.getEntry(
195            dict(customer_id=self.customer.customer_id), self.app) is self.customer
196
197    def test_addEntry(self):
198        new_customer = Customer()
199        self.processor.addEntry(
200            new_customer, dict(), self.app)
201        assert len(self.app['customers'].keys()) == 2
202
203    def test_checkConversion(self):
204        # Make sure we can check conversions and that the cust_id
205        # counter is not raised during such checks.
206        initial_cust_id = self.app['customers']._curr_cust_id
207        errs, inv_errs, conv_dict = self.processor.checkConversion(
208            dict(reg_number='1', state='approved'))
209        self.assertEqual(len(errs),0)
210        # Empty state is allowed
211        errs, inv_errs, conv_dict = self.processor.checkConversion(
212            dict(reg_number='1', state=''))
213        self.assertEqual(len(errs),0)
214        #self.assertTrue(('state', 'no value provided') in errs)
215        errs, inv_errs, conv_dict = self.processor.checkConversion(
216            dict(reg_number='1', state='nonsense'))
217        self.assertEqual(len(errs),1)
218        self.assertTrue(('state', 'not allowed') in errs)
219        new_cust_id = self.app['customers']._curr_cust_id
220        self.assertEqual(initial_cust_id, new_cust_id)
221        return
222
223    def test_checkUpdateRequirements(self):
224        # Make sure that pg customers can't be updated with wrong transition.
225        err = self.processor.checkUpdateRequirements(self.customer,
226            dict(reg_number='1', state='returning'), self.app)
227        self.assertTrue(err is None)
228
229    def test_delEntry(self):
230        assert self.customer.customer_id in self.app['customers'].keys()
231        self.processor.delEntry(
232            dict(reg_number=self.customer.reg_number), self.app)
233        assert self.customer.customer_id not in self.app['customers'].keys()
234
235    def test_import(self):
236        self.assertEqual(self.app['customers']._curr_cust_id, 1000001)
237        num, num_warns, fin_file, fail_file = self.processor.doImport(
238            self.csv_file, CUSTOMER_HEADER_FIELDS)
239        self.assertEqual(num_warns,0)
240        self.assertEqual(len(self.app['customers']), 10)
241        self.assertEqual(self.app['customers']['X666666'].reg_number,'1')
242        self.assertEqual(
243            self.app['customers']['X666666'].state, 'requested')
244        # Two new customer_ids have been created.
245        self.assertEqual(self.app['customers']._curr_cust_id, 1000003)
246        shutil.rmtree(os.path.dirname(fin_file))
247
248    def test_import_update(self):
249        num, num_warns, fin_file, fail_file = self.processor.doImport(
250            self.csv_file, CUSTOMER_HEADER_FIELDS)
251        shutil.rmtree(os.path.dirname(fin_file))
252        num, num_warns, fin_file, fail_file = self.processor.doImport(
253            self.csv_file_update, CUSTOMER_HEADER_FIELDS_UPDATE, 'update')
254        self.assertEqual(num_warns,0)
255        # state has changed
256        self.assertEqual(self.app['customers']['X666666'].state,'approved')
257        # state has not changed
258        self.assertEqual(self.app['customers']['Y777777'].state, 'requested')
259        shutil.rmtree(os.path.dirname(fin_file))
260
261    def test_import_duplicate_data(self):
262        num, num_warns, fin_file, fail_file = self.processor.doImport(
263            self.csv_file_duplicates, CUSTOMER_HEADER_FIELDS_DUPLICATES)
264        content = open(fail_file).read()
265        self.assertEqual(num_warns,2)
266        self.assertEqual(
267            content,
268            'reg_number,password,firstname,lastname,sex,phone,state,date_of_birth,customer_id,email,--ERRORS--\r\n'
269            '1,,Frank,Meyer,m,1234,,1990-01-06,F123456,aa@aa.ng,reg_number: reg_number\r\n'
270            '3,,Uli,Schulz,m,1234,,1990-01-07,A123456,aa@aa.ng,This object already exists. Skipping.\r\n'
271
272            )
273        shutil.rmtree(os.path.dirname(fin_file))
274
275class CustomerDocumentProcessorTest(CustomerImportExportSetup):
276
277    def setUp(self):
278        super(CustomerDocumentProcessorTest, self).setUp()
279
280        customer = Customer()
281        self.app['customers'].addCustomer(customer)
282        customer = self.setup_customer(customer)
283        notify(grok.ObjectModifiedEvent(customer))
284        self.customer = self.app['customers'][customer.customer_id]
285
286        # Import customers with subobjects
287        customer_file = os.path.join(self.workdir, 'sample_customer_data.csv')
288        open(customer_file, 'wb').write(CUSTOMER_SAMPLE_DATA)
289        num, num_warns, fin_file, fail_file = CustomerProcessor().doImport(
290            customer_file, CUSTOMER_HEADER_FIELDS)
291        shutil.rmtree(os.path.dirname(fin_file))
292
293        self.processor = CustomerDocumentProcessor()
294        self.csv_file = os.path.join(
295            self.workdir, 'sample_document_data.csv')
296        open(self.csv_file, 'wb').write(DOCUMENT_SAMPLE_DATA)
297
298    def test_interface(self):
299        # Make sure we fulfill the interface contracts.
300        assert verifyObject(IBatchProcessor, self.processor) is True
301        assert verifyClass(
302            IBatchProcessor, CustomerDocumentProcessor) is True
303
304    def test_getEntry(self):
305        assert self.processor.getEntry(
306            dict(customer_id='ID_NONE', document_id='nonsense'), self.app) is None
307        assert self.processor.getEntry(
308            dict(customer_id=self.customer.customer_id, document_id='DOC1'),
309            self.app) is self.customer['documents']['DOC1']
310
311    def test_delEntry(self):
312        assert self.processor.getEntry(
313            dict(customer_id=self.customer.customer_id, document_id='DOC1'),
314            self.app) is self.customer['documents']['DOC1']
315        self.assertEqual(len(self.customer['documents'].keys()),1)
316        self.processor.delEntry(
317            dict(customer_id=self.customer.customer_id, document_id='DOC1'),
318            self.app)
319        assert self.processor.getEntry(
320            dict(customer_id=self.customer.customer_id, document_id='DOC1'),
321            self.app) is None
322        self.assertEqual(len(self.customer['documents'].keys()),0)
323
324    def test_addEntry(self):
325        self.assertEqual(len(self.customer['documents'].keys()),1)
326        document1 = createObject(u'waeup.CustomerSampleDocument')
327        document1.document_id = u'd234'
328        self.processor.addEntry(
329            document1, dict(customer_id=self.customer.customer_id, document_id='d234'),
330            self.app)
331        self.assertEqual(len(self.customer['documents'].keys()),2)
332        self.assertEqual(self.customer['documents']['d234'].document_id, 'd234')
333
334    def test_checkConversion(self):
335        errs, inv_errs, conv_dict = self.processor.checkConversion(
336            dict(document_id='DOC', class_name='CustomerSampleDocument'))
337        self.assertEqual(len(errs),0)
338        #errs, inv_errs, conv_dict = self.processor.checkConversion(
339        #    dict(document_id='id with spaces', class_name='CustomerSampleDocument'))
340        #self.assertEqual(len(errs),1)
341        errs, inv_errs, conv_dict = self.processor.checkConversion(
342            dict(document_id='DOC', class_name='WrongDocument'))
343        self.assertEqual(len(errs),1)
344
345    def test_import(self):
346        num, num_warns, fin_file, fail_file = self.processor.doImport(
347            self.csv_file, DOCUMENT_HEADER_FIELDS,'create')
348        fail_file = open(fail_file).read()
349        self.assertEqual(num_warns,1)
350        # document_id must be unique
351        self.assertEqual(fail_file,
352            'class_name,reg_number,document_id,title,--ERRORS--\r\n'
353            'CustomerSampleDocument,2,d5,My stolen doc,'
354            'This object already exists. Skipping.\r\n')
355        document = self.processor.getEntry(dict(reg_number='1',
356            document_id='d3'), self.app)
357        self.assertEqual(
358            self.app['customers']['X666666']['documents']['d3'],
359            document)
360        self.assertEqual(document.document_id, 'd3')
361        document = self.processor.getEntry(dict(reg_number='3',
362            document_id='d5'), self.app)
363        shutil.rmtree(os.path.dirname(fin_file))
364        logcontent = open(self.logfile).read()
365        # Fetch uuid
366        docid = [i for i in self.app['customers']['X666666']['documents'].keys()
367            if len(i) > 10][0]
368         # Logging message from updateEntry
369        self.assertTrue(
370            'INFO - system - CustomerDocument Processor - '
371            'sample_document_data - X666666 - %s - updated: title=My 4th doc'
372            % docid in logcontent)
373
374    def test_import_update(self):
375        # We perform the same import twice,
376        # the second time in update mode. The number
377        # of warnings must be the same.
378        num, num_warns, fin_file, fail_file = self.processor.doImport(
379            self.csv_file, DOCUMENT_HEADER_FIELDS,'create')
380        shutil.rmtree(os.path.dirname(fin_file))
381        num, num_warns, fin_file, fail_file = self.processor.doImport(
382            self.csv_file, DOCUMENT_HEADER_FIELDS,'update')
383        # There is one record without document_id
384        self.assertEqual(num_warns,1)
385        shutil.rmtree(os.path.dirname(fin_file))
386
387    def test_import_remove(self):
388        # We perform the same import twice,
389        # the second time in remove mode. The number
390        # of warnings must be the same.
391        num, num_warns, fin_file, fail_file = self.processor.doImport(
392            self.csv_file, DOCUMENT_HEADER_FIELDS,'create')
393        shutil.rmtree(os.path.dirname(fin_file))
394        num, num_warns, fin_file, fail_file = self.processor.doImport(
395            self.csv_file, DOCUMENT_HEADER_FIELDS,'remove')
396        # There is one record without document_id and one duplicate
397        self.assertEqual(num_warns,2)
398        shutil.rmtree(os.path.dirname(fin_file))
399        logcontent = open(self.logfile).read()
400        self.assertTrue(
401            'INFO - system - K1000001 - Document removed: d5'
402            in logcontent)
403
404
405class ContractProcessorTest(CustomerImportExportSetup):
406
407    def setUp(self):
408        super(ContractProcessorTest, self).setUp()
409
410        customer = Customer()
411        self.app['customers'].addCustomer(customer)
412        customer = self.setup_customer(customer)
413        notify(grok.ObjectModifiedEvent(customer))
414        self.customer = self.app['customers'][customer.customer_id]
415
416        # Import customers with subobjects
417        customer_file = os.path.join(self.workdir, 'sample_customer_data.csv')
418        open(customer_file, 'wb').write(CUSTOMER_SAMPLE_DATA)
419        num, num_warns, fin_file, fail_file = CustomerProcessor().doImport(
420            customer_file, CUSTOMER_HEADER_FIELDS)
421        shutil.rmtree(os.path.dirname(fin_file))
422
423        self.processor = ContractProcessor()
424        self.csv_file = os.path.join(
425            self.workdir, 'sample_contract_data.csv')
426        open(self.csv_file, 'wb').write(CONTRACT_SAMPLE_DATA)
427
428    def test_interface(self):
429        # Make sure we fulfill the interface contracts.
430        assert verifyObject(IBatchProcessor, self.processor) is True
431        assert verifyClass(
432            IBatchProcessor, ContractProcessor) is True
433
434    def test_getEntry(self):
435        assert self.processor.getEntry(
436            dict(customer_id='ID_NONE', contract_id='nonsense'), self.app) is None
437        assert self.processor.getEntry(
438            dict(customer_id=self.customer.customer_id, contract_id='c101'),
439            self.app) is self.customer['contracts']['c101']
440
441    def test_delEntry(self):
442        assert self.processor.getEntry(
443            dict(customer_id=self.customer.customer_id, contract_id='c101'),
444            self.app) is self.customer['contracts']['c101']
445        self.assertEqual(len(self.customer['contracts'].keys()),1)
446        self.processor.delEntry(
447            dict(customer_id=self.customer.customer_id, contract_id='c101'),
448            self.app)
449        assert self.processor.getEntry(
450            dict(customer_id=self.customer.customer_id, contract_id='c101'),
451            self.app) is None
452        self.assertEqual(len(self.customer['contracts'].keys()),0)
453
454    def test_addEntry(self):
455        self.assertEqual(self.app._curr_con_id, 102)
456        self.assertEqual(len(self.customer['contracts'].keys()),1)
457        contract1 = createObject(u'waeup.SampleContract')
458        contract1.contract_id = 'c234'
459        self.processor.addEntry(
460            contract1, dict(customer_id=self.customer.customer_id, contract_id='c234'),
461            self.app)
462        self.assertEqual(len(self.customer['contracts'].keys()),2)
463        self.assertEqual(self.customer['contracts']['c234'].contract_id, 'c234')
464        # _curr_con_id hasn't changed
465        self.assertEqual(self.app._curr_con_id, 102)
466
467    def test_checkConversion(self):
468        self.assertEqual(self.app._curr_con_id, 102)
469        errs, inv_errs, conv_dict = self.processor.checkConversion(
470            dict(contract_id='c126', class_name='SampleContract',
471                 document_object='DOC1', product_object='SAM'))
472        self.assertEqual(len(errs),0)
473        errs, inv_errs, conv_dict = self.processor.checkConversion(
474            dict(contract_id='nonsense', class_name='SampleContract'))
475        self.assertEqual(len(errs),1)
476        errs, inv_errs, conv_dict = self.processor.checkConversion(
477            dict(contract_id='c127', class_name='WrongContract'))
478        self.assertEqual(len(errs),1)
479        # _curr_con_id hasn't changed
480        self.assertEqual(self.app._curr_con_id, 102)
481
482    def test_import(self):
483        num, num_warns, fin_file, fail_file = self.processor.doImport(
484            self.csv_file, CONTRACT_HEADER_FIELDS,'create')
485        self.assertEqual(num_warns,3)
486        # contract_id must be unique
487        fail_file = open(fail_file).read()
488        self.assertEqual(fail_file,
489            'reg_number,contract_id,title,class_name,product_object,'
490            'document_object,--ERRORS--\r\n'
491            '2,c5,My stolen contract,SampleContract,SAM,DOC1,'
492            'contract_id: id exists\r\n'
493            '3,c6,My 5th contract,SampleContract,SAM,NONEXISTENT,'
494            'document_object: Invalid value\r\n'
495            '3,c7,My sixt contract,SampleContract,NONEXISTENT,DOC1,'
496            'product_object: Invalid value\r\n')
497        contract = self.processor.getEntry(dict(reg_number='1',
498            contract_id='c3'), self.app)
499        self.assertEqual(
500            self.app['customers']['X666666']['contracts']['c3'],
501            contract)
502        self.assertEqual(contract.contract_id, 'c3')
503        contract = self.processor.getEntry(dict(reg_number='3',
504            contract_id='c5'), self.app)
505        shutil.rmtree(os.path.dirname(fin_file))
506        logcontent = open(self.logfile).read()
507        # Logging message from updateEntry
508        self.assertTrue(
509            'INFO - system - Contract Processor - '
510            'sample_contract_data - X666666 - updated: '
511            'contract_id=c3, title=My first contract'
512            in logcontent)
513
514    def test_import_update(self):
515        # We perform the same import twice,
516        # the second time in update mode. The number
517        # of warnings must be the same.
518        num, num_warns, fin_file, fail_file = self.processor.doImport(
519            self.csv_file, CONTRACT_HEADER_FIELDS,'create')
520        shutil.rmtree(os.path.dirname(fin_file))
521        num, num_warns, fin_file, fail_file = self.processor.doImport(
522            self.csv_file, CONTRACT_HEADER_FIELDS,'update')
523        # There are one record without contract_id, one duplicate and two
524        # records with wrong object identifier.
525        self.assertEqual(num_warns,4)
526        shutil.rmtree(os.path.dirname(fin_file))
527
528    def test_import_remove(self):
529        # We perform the same import twice,
530        # the second time in remove mode. The number
531        # of warnings must be the same.
532        num, num_warns, fin_file, fail_file = self.processor.doImport(
533            self.csv_file, CONTRACT_HEADER_FIELDS,'create')
534        shutil.rmtree(os.path.dirname(fin_file))
535        num, num_warns, fin_file, fail_file = self.processor.doImport(
536            self.csv_file, CONTRACT_HEADER_FIELDS,'remove')
537        # There are one record without contract_id, one duplicate and two
538        # records with wrong object identifier.
539        self.assertEqual(num_warns,4)
540        shutil.rmtree(os.path.dirname(fin_file))
541        logcontent = open(self.logfile).read()
542        self.assertTrue(
543            'INFO - system - K1000001 - Contract removed: c5'
544            in logcontent)
Note: See TracBrowser for help on using the repository browser.