correct tests, the way god intended
This commit is contained in:
parent
1571221293
commit
6dbd58faf4
317
addbook.rex
317
addbook.rex
@ -18,7 +18,8 @@ app~run()
|
||||
Do forever
|
||||
if .environment['STOPNOW'] = 1 then do
|
||||
app~cleanup() /* Clean up before exiting */
|
||||
address system 'clear'
|
||||
call SysCls
|
||||
Say .environment["REXX_PATH"]
|
||||
Say "Exiting Address Book."
|
||||
exit 0
|
||||
end
|
||||
@ -27,18 +28,6 @@ end
|
||||
|
||||
Exit
|
||||
|
||||
::routine SysWait
|
||||
use arg seconds
|
||||
address system 'sleep' seconds
|
||||
return
|
||||
|
||||
|
||||
::ROUTINE setEnv
|
||||
.local~home = SysGetpwnam("gmgauthier", "d")
|
||||
.local~projectRoot = .home||"/Projects/rexx-address-book"
|
||||
.local~dbPath = .projectRoot||"/db/contacts.sqlite"
|
||||
|
||||
|
||||
::CLASS AddressBookApp PUBLIC
|
||||
|
||||
::METHOD Init
|
||||
@ -64,300 +53,20 @@ Exit
|
||||
db~closeDb()
|
||||
return
|
||||
|
||||
::ROUTINE setEnv
|
||||
.environment~home = SysGetpwnam("gmgauthier", "d")
|
||||
.environment~projectRoot = .home||"/Projects/rexx-address-book"
|
||||
.environment~pkgPath = .projectRoot||"/app"
|
||||
.environment~dbPath = .projectRoot||"/db"
|
||||
.environment["REXX_PATH"] = .projectRoot||";"||.pkgPath||";"||.dbPath||";"
|
||||
|
||||
::CLASS AddressBookDB PUBLIC
|
||||
|
||||
::method init
|
||||
expose db
|
||||
|
||||
/* Create database directory if it doesn't exist */
|
||||
if SysFileExists(.dbPath) == .false then DO
|
||||
SAY "Initializing new address book"
|
||||
db = .ooSQLiteConnection~new(.dbPath)
|
||||
self~createTables
|
||||
END
|
||||
Else Do
|
||||
db = .ooSQLiteConnection~new(.dbPath,.ooSQLite~OPEN_READWRITE)
|
||||
End
|
||||
return
|
||||
|
||||
::METHOD getFileName
|
||||
expose db
|
||||
return db~fileName()
|
||||
|
||||
::METHOD closeDb
|
||||
expose db
|
||||
db~Close()
|
||||
RETURN
|
||||
|
||||
::method createTables
|
||||
expose db
|
||||
|
||||
/* Contacts table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS contacts ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"first_name TEXT,"||,
|
||||
"last_name TEXT,"||,
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"||,
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"||,
|
||||
")")
|
||||
|
||||
/* Phone numbers table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS phone_numbers ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, mobile, etc.
|
||||
"number TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Email addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS email_addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"email TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Physical addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"street TEXT,"||,
|
||||
"city TEXT,"||,
|
||||
"state TEXT,"||,
|
||||
"postal_code TEXT,"||,
|
||||
"country TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
return
|
||||
|
||||
/* UI handling class */
|
||||
::class AddressBookUI public
|
||||
|
||||
::method init
|
||||
expose win mainMenu
|
||||
return
|
||||
|
||||
::method initialize
|
||||
expose win mainMenu
|
||||
|
||||
win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
|
||||
self~setupMainMenu(win)
|
||||
return
|
||||
|
||||
::METHOD DrawMainPanel
|
||||
use arg clrset, with_box
|
||||
win = .window~new() /* The first invocation must be with no arguments */
|
||||
win~curs_set(0) /* Hide the cursor by default */
|
||||
win~keypad(1) /* Enable function keys and arrow keys */
|
||||
win~raw
|
||||
win~noecho
|
||||
win~clear
|
||||
win~cbreak
|
||||
|
||||
self~SetPanelColor(win, clrset)
|
||||
|
||||
if with_box = .true then DO
|
||||
win~box(0, 0) /* Draw box with default ACS characters */
|
||||
win~refresh
|
||||
END
|
||||
|
||||
return win
|
||||
|
||||
::METHOD DrawSubPanel
|
||||
use arg height, width, starty, startx, clrset, title, with_box
|
||||
new_win = .window~new(height, width, starty, startx)
|
||||
new_win~curs_set(0)
|
||||
|
||||
/* Set the panel color */
|
||||
self~SetPanelColor(new_win, clrset)
|
||||
|
||||
/* Display title */
|
||||
new_win~attron(new_win~A_BOLD)
|
||||
new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
|
||||
new_win~attroff(new_win~A_BOLD)
|
||||
new_win~refresh
|
||||
if with_box = .true then DO
|
||||
new_win~box(0,0)
|
||||
new_win~refresh
|
||||
END
|
||||
return new_win
|
||||
|
||||
::METHOD SetPanelColor
|
||||
use arg panel, clrset
|
||||
|
||||
panel~start_color()
|
||||
SELECT
|
||||
/* white text on blue background */
|
||||
when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
|
||||
/* white text on grey background */
|
||||
when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
|
||||
/* yellow text on red background */
|
||||
when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
|
||||
/* black text on white background */
|
||||
when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
|
||||
/* black text on green background */
|
||||
when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
|
||||
/* black text on cyan background */
|
||||
when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
|
||||
/* Default to white text on black background */
|
||||
OTHERWISE DO
|
||||
clrset=0
|
||||
panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
|
||||
END
|
||||
END
|
||||
panel~bkgd(panel~color_pair(clrset+1))
|
||||
panel~refresh
|
||||
|
||||
::method setupMainMenu
|
||||
expose mainMenu win menuwin menuItems menu_keys
|
||||
use arg win
|
||||
|
||||
max_y = win~lines
|
||||
max_x = win~cols
|
||||
|
||||
menu_height = 21
|
||||
menu_width = 40
|
||||
start_y = (max_y - menu_height) % 2
|
||||
start_x = (max_x - menu_width) % 2
|
||||
clrset=1
|
||||
title = "Rexx Address Book"
|
||||
with_box = .true
|
||||
|
||||
menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
|
||||
menuItems = .array~of("[A]dd Contact", "[R]emove Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "E[X]it")
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
/* Display menu items */
|
||||
selected = 1
|
||||
self~DrawMenu(menuwin, menuItems, selected, win)
|
||||
/* menuwin~mvaddstr(menu_height - 2, 3, "Type 'X' to exit") */
|
||||
menuwin~refresh
|
||||
RETURN
|
||||
|
||||
::method DrawMenu
|
||||
expose win menuItems selected mainwin
|
||||
use arg win, items, selected, mainwin
|
||||
|
||||
do i = 1 to items~items
|
||||
if i = selected then do
|
||||
win~attron(mainwin~COLOR_PAIR(2))
|
||||
win~attron(mainwin~A_BOLD)
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
win~attroff(mainwin~COLOR_PAIR(2))
|
||||
win~attroff(mainwin~A_BOLD)
|
||||
end
|
||||
else do
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
end
|
||||
end
|
||||
win~refresh
|
||||
return
|
||||
|
||||
/** MAIN LOOP **/
|
||||
::method mainLoop
|
||||
expose win mainMenu menuwin
|
||||
|
||||
running = .true
|
||||
|
||||
do while running
|
||||
menuwin~refresh
|
||||
key = win~getch
|
||||
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
select
|
||||
when key = win~KEY_UP then do
|
||||
if selected > 1 then selected = selected - 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = win~KEY_DOWN then do
|
||||
if selected < menuItems~items then selected = selected + 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
|
||||
menuwin~refresh
|
||||
call ProcessSelection menu_keys[selected], home
|
||||
return
|
||||
end
|
||||
when key = D2C(88) | key = D2C(120) | key = "x" | key = C2D("x") then do
|
||||
menuwin~endwin
|
||||
.environment['STOPNOW'] = 1
|
||||
RETURN
|
||||
end
|
||||
otherwise do
|
||||
if datatype(key) = 'CHAR' then do
|
||||
key = lower(key)
|
||||
pos = self~findInArray(menu_keys, key)
|
||||
if pos > 0 then do
|
||||
menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
|
||||
menuwin~refresh
|
||||
call SysWait 0.5
|
||||
self~ProcessSelection(menuwin, key)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end /* do while running */
|
||||
return
|
||||
|
||||
/* Process selection */
|
||||
::METHOD ProcessSelection
|
||||
use arg menuwin, key_char
|
||||
select
|
||||
when key_char = 'a' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the ADD panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'r' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the REMOVE panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'e' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the EDIT panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 's' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the SEARCH panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'l' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the LIST panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
otherwise nop
|
||||
end
|
||||
return
|
||||
|
||||
::METHOD findInArray
|
||||
use arg array, item
|
||||
do i = 1 to array~items
|
||||
if array[i] = item then return i
|
||||
end
|
||||
return 0 /* Not found */
|
||||
|
||||
::method cleanup
|
||||
expose win
|
||||
/* Clean up ncurses */
|
||||
win~endwin
|
||||
return
|
||||
|
||||
/* Handle program termination */
|
||||
::ROUTINE ProgramHalt
|
||||
signal off halt
|
||||
Say "PROGRAM TERMINATED AT THE KEYBOARD"
|
||||
exit 0
|
||||
|
||||
/** External Libraries **/
|
||||
::requires 'app/appdb.cls'
|
||||
::requires 'app/appui.cls'
|
||||
::requires 'app/utils.rex'
|
||||
|
||||
::requires 'ooSQLite.cls'
|
||||
::requires "rxunixsys" LIBRARY
|
||||
::requires 'ncurses.cls'
|
||||
|
||||
|
411
app/appdb.cls
Normal file
411
app/appdb.cls
Normal file
@ -0,0 +1,411 @@
|
||||
::requires 'ooSQLite.cls'
|
||||
::requires "rxunixsys" LIBRARY
|
||||
::requires 'ncurses.cls'
|
||||
::requires 'app/utils.rex'
|
||||
|
||||
::CLASS AddressBookDB PUBLIC
|
||||
|
||||
::method init
|
||||
expose db
|
||||
|
||||
/* Create database directory if it doesn't exist */
|
||||
if SysFileExists(.dbPath) == .false then DO
|
||||
SAY "Initializing new address book"
|
||||
db = .ooSQLiteConnection~new(.dbPath)
|
||||
self~createTables
|
||||
END
|
||||
Else Do
|
||||
Say "Initializing existing address book"
|
||||
db = .ooSQLiteConnection~new(.dbPath,.ooSQLite~OPEN_READWRITE)
|
||||
End
|
||||
return
|
||||
|
||||
::METHOD getFileName
|
||||
expose db
|
||||
return db~fileName()
|
||||
|
||||
::METHOD closeDb
|
||||
expose db
|
||||
db~Close()
|
||||
RETURN
|
||||
|
||||
::METHOD createTables
|
||||
expose db
|
||||
|
||||
/* Contacts table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS contacts ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"first_name TEXT,"||,
|
||||
"last_name TEXT,"||,
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"||,
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"||,
|
||||
")")
|
||||
|
||||
/* Phone numbers table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS phone_numbers ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, mobile, etc.
|
||||
"number TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Email addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS email_addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"email TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Physical addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"street TEXT,"||,
|
||||
"city TEXT,"||,
|
||||
"state TEXT,"||,
|
||||
"postal_code TEXT,"||,
|
||||
"country TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
return
|
||||
|
||||
/* Contact CRUD Operations */
|
||||
::METHOD addContact
|
||||
expose db
|
||||
use arg contactDict /* Use a Rexx 'directory' to pass multiple values around */
|
||||
sql = "INSERT INTO contacts (first_name, last_name) VALUES ('"contactDict~firstName"', '"contactDict~lastName"')"
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then do
|
||||
say "Error adding contact:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
contactId = db~lastInsertRowId() /* the row id of 'contacts' table is the master id */
|
||||
self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"])
|
||||
self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"])
|
||||
self~addRealAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state,
|
||||
contactDict~postalCode, contactDict~country)
|
||||
return contactId
|
||||
|
||||
::METHOD getContact
|
||||
expose db
|
||||
use arg contactId
|
||||
returnedContent = .Directory~new()
|
||||
sql1 = "SELECT * FROM contacts WHERE id = "contactId
|
||||
contacts = db~exec(sql1,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
/* The result will be rendered as an array of Rexx 'directories'
|
||||
* which are basically analogous to python dictionaries. */
|
||||
|
||||
if contacts = .nil then DO
|
||||
Say 'NO CONTACTS FOUND'
|
||||
return
|
||||
END
|
||||
if contacts~size < 1 then do
|
||||
Say 'NO CONTACT FOUND'
|
||||
return
|
||||
END
|
||||
return contacts[1] /* Rexx is 1-indexed */
|
||||
/***
|
||||
returnedContent~firstName = contact["FIRST_NAME"]
|
||||
returnedContent~lastName = contact["LAST_NAME"]
|
||||
sql2 = "SELECT * FROM email_addresses WHERE contact_id = "contactId
|
||||
emails = db~exec(sql2,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
email_array = .Array~new()
|
||||
do row over emails
|
||||
email_array.append(row["EMAIL"])
|
||||
END
|
||||
returnedContent~emailAddresses = email_array
|
||||
|
||||
sql3 = "SELECT * FROM phone_numbers WHERE contact_id = "contactId
|
||||
phones = db~exec(sql3,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
phone_dir = .Directory~new()
|
||||
phone_array = .Array~new()
|
||||
do phone over phones
|
||||
phone_dir~put(phone["NUMBER"], phone["TYPE"])
|
||||
phone_array.append(phone_dir)
|
||||
END
|
||||
returnedContent~phoneNumbers = phone_array
|
||||
|
||||
sql4 = "SELECT * FROM addresses WHERE contact_id = "contactId
|
||||
addresses = db~exec(sql3,.true,.ooSQLite~~OO_ARRAY_OF_DIRECTORIES)
|
||||
address_dir = .Dictionary~new()
|
||||
address_array = .Array~new()
|
||||
do address over addresses
|
||||
address_dir~type = address["TYPE"]
|
||||
address_dir~street = address["STREET"]
|
||||
address_dir~city = address["CITY"]
|
||||
address_dir~state = address["STATE"]
|
||||
address_dir~postal_code = address["POSTAL_CODE"]
|
||||
address_dir~country = address["COUNTRY"]
|
||||
address_array.append(address_dir)
|
||||
END
|
||||
returnedContent~addresses = address_array
|
||||
return returnedContent
|
||||
***/
|
||||
|
||||
::METHOD getAllContacts
|
||||
expose db
|
||||
sql = "SELECT * FROM contacts ORDER BY last_name, first_name"
|
||||
contacts = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
return contacts
|
||||
|
||||
::METHOD searchContacts
|
||||
expose db
|
||||
use arg searchTerm
|
||||
|
||||
contactsList = .Array~new()
|
||||
|
||||
sql = "SELECT id FROM contacts WHERE first_name LIKE '%"searchTerm"%' OR last_name LIKE '%"searchTerm"%' ORDER BY last_name, first_name"
|
||||
contactIds = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
|
||||
do contactDir over contactIds
|
||||
contactId = contactDir["ID"]
|
||||
contact = self~getContact(contactId)
|
||||
if contact \= .nil then do
|
||||
contactsList~append(contact)
|
||||
end
|
||||
end
|
||||
return contactsList
|
||||
|
||||
::METHOD updateContact
|
||||
expose db
|
||||
use arg contactId, firstName, lastName
|
||||
sql = "UPDATE contacts SET first_name = '"firstName"', last_name = '"lastName"' WHERE id = "contactId
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then do
|
||||
say "Error updating contact:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
return rc
|
||||
/**
|
||||
self~removeContactPhones(contactId)
|
||||
self~removeContactEmails(contactId)
|
||||
self~removeContactAddresses(contactId)
|
||||
|
||||
/* Then add new entries */
|
||||
self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"])
|
||||
self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"])
|
||||
self~addAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state,
|
||||
contactDict~postalCode, contactDict~country)
|
||||
return 0
|
||||
**/
|
||||
|
||||
::METHOD deleteContact
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
/* Delete all related records first (assuming foreign key constraints) */
|
||||
self~removeContactPhones(contactId)
|
||||
self~removeContactEmails(contactId)
|
||||
self~removeContactAddresses(contactId)
|
||||
|
||||
/* Delete the contact record */
|
||||
sql = "DELETE FROM contacts WHERE id = "contactId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error deleting contact:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
|
||||
::METHOD getPhoneNumbers
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
phones = .Array~new
|
||||
sql = "SELECT id, type, number FROM phone_numbers WHERE contact_id = "contactId
|
||||
phones = db~exec(sql,.true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
|
||||
return phones
|
||||
|
||||
::METHOD addPhoneNumber
|
||||
expose db
|
||||
use arg contactId, phoneType, phoneNumber
|
||||
|
||||
if phoneType = .nil | phoneNumber = .nil then return 0
|
||||
|
||||
sql = "INSERT INTO phone_numbers (contact_id, type, number) VALUES ("contactId", '"phoneType"', '"phoneNumber"')"
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then DO
|
||||
Say "Unable to insert phone number for contact id "contactId
|
||||
return rc
|
||||
END
|
||||
phone_id = db~lastInsertRowId()
|
||||
return phone_id
|
||||
|
||||
::METHOD updatePhoneNumber
|
||||
expose db
|
||||
use arg phoneId, phoneType, phoneNumber
|
||||
|
||||
sql = "UPDATE phone_numbers SET type = '"phoneType"', number = '"phoneNumber"' WHERE id = "phoneId
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then DO
|
||||
Say "Unable to update phone number or type for phone id "contactId
|
||||
return rc
|
||||
END
|
||||
|
||||
return rc
|
||||
|
||||
::METHOD deletePhoneNumber
|
||||
expose db
|
||||
use arg phoneId
|
||||
|
||||
stmt = db~prepare("DELETE FROM phone_numbers WHERE id = ?")
|
||||
stmt~bind(1, phoneId)
|
||||
stmt~step
|
||||
|
||||
return db~changes() > 0
|
||||
|
||||
|
||||
|
||||
/* Email address operations */
|
||||
|
||||
::METHOD addEmailAddress
|
||||
expose db
|
||||
use arg contactId, emailType, emailAddress
|
||||
|
||||
if emailType = .nil | emailAddress = .nil then return 0
|
||||
|
||||
sql = "INSERT INTO email_addresses (contact_id, type, email) VALUES ("contactId", '"emailType"', '"emailAddress"')"
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then do
|
||||
say "Error adding email address:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
|
||||
email_id = db~lastInsertRowId()
|
||||
|
||||
return email_id
|
||||
|
||||
::METHOD getEmailAddresses
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
sql = "SELECT id, type, email FROM email_addresses WHERE contact_id = "contactId
|
||||
emails = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
return emails
|
||||
|
||||
::METHOD updateEmailAddress
|
||||
expose db
|
||||
use arg emailId, emailType, emailAddress
|
||||
|
||||
sql = "UPDATE email_addresses SET type = '"emailType"', email = '"emailaddress"' WHERE id = "emailId
|
||||
rc = db~exec(sql)
|
||||
if rc \= 0 then do
|
||||
say "Error adding email address:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
return rc
|
||||
|
||||
|
||||
::METHOD deleteEmailAddress
|
||||
expose db
|
||||
use arg emailId
|
||||
|
||||
stmt = db~prepare("DELETE FROM email_addresses WHERE id = ?")
|
||||
stmt~bind(1, emailId)
|
||||
stmt~step
|
||||
|
||||
return db~changes() > 0
|
||||
|
||||
/* Physical address operations */
|
||||
|
||||
::METHOD addRealAddress
|
||||
expose db
|
||||
use arg contactId, addressType, street, city, state, postalCode, country
|
||||
|
||||
if addressType = .nil | street = .nil then return 0
|
||||
|
||||
sql = "INSERT INTO addresses (contact_id, type, street, city, state, postal_code, country)",
|
||||
"VALUES ("contactId", '"addressType"', '"street"', '"city"', '"state"', '"postalCode"', '"country"')"
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error adding address:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
addr_id = db~lastInsertRowId()
|
||||
return addr_id
|
||||
|
||||
::METHOD getRealAddresses
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
sql = "SELECT id, type, street, city, state, postal_code, country" || ,
|
||||
" FROM addresses WHERE contact_id = " contactId
|
||||
addresses = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES)
|
||||
return addresses
|
||||
|
||||
::METHOD updateRealAddress
|
||||
expose db
|
||||
use arg addressId, addressType, street, city, state, postalCode, country
|
||||
|
||||
sql = "UPDATE addresses SET type = '"addressType"', street = '"street"', city = '"city"'," || ,
|
||||
" state = '"state"', postal_code = '"postalCode"', country = '"country"' WHERE id = "addressId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error updating address:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
|
||||
return rc
|
||||
|
||||
::METHOD deleteAddress
|
||||
expose db
|
||||
use arg addressId
|
||||
|
||||
sql="DELETE FROM addresses WHERE id = "addressId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error removing address:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
|
||||
return 0
|
||||
|
||||
::METHOD removeContactPhones
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
sql = "DELETE FROM phone_numbers WHERE contact_id = "contactId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error removing phone numbers:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
|
||||
return 0
|
||||
|
||||
::METHOD removeContactEmails
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
sql = "DELETE FROM email_addresses WHERE contact_id = "contactId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error removing email addresses:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
|
||||
::METHOD removeContactAddresses
|
||||
expose db
|
||||
use arg contactId
|
||||
|
||||
sql = "DELETE FROM addresses WHERE contact_id = "contactId
|
||||
rc = db~exec(sql)
|
||||
|
||||
if rc \= 0 then do
|
||||
say "Error removing addresses:" db~errMsg()
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
|
220
app/appui.cls
Normal file
220
app/appui.cls
Normal file
@ -0,0 +1,220 @@
|
||||
::requires 'ooSQLite.cls'
|
||||
::requires "rxunixsys" LIBRARY
|
||||
::requires 'ncurses.cls'
|
||||
::requires 'app/utils.rex'
|
||||
|
||||
::class AddressBookUI public
|
||||
|
||||
::method init
|
||||
expose win mainMenu
|
||||
return
|
||||
|
||||
::method initialize
|
||||
expose win mainMenu
|
||||
|
||||
win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
|
||||
self~setupMainMenu(win)
|
||||
return
|
||||
|
||||
::METHOD DrawMainPanel
|
||||
use arg clrset, with_box
|
||||
win = .window~new() /* The first invocation must be with no arguments */
|
||||
win~curs_set(0) /* Hide the cursor by default */
|
||||
win~keypad(1) /* Enable function keys and arrow keys */
|
||||
win~raw
|
||||
win~noecho
|
||||
win~clear
|
||||
win~cbreak
|
||||
|
||||
self~SetPanelColor(win, clrset)
|
||||
|
||||
if with_box = .true then DO
|
||||
win~box(0, 0) /* Draw box with default ACS characters */
|
||||
win~refresh
|
||||
END
|
||||
|
||||
return win
|
||||
|
||||
::METHOD DrawSubPanel
|
||||
use arg height, width, starty, startx, clrset, title, with_box
|
||||
new_win = .window~new(height, width, starty, startx)
|
||||
new_win~curs_set(0)
|
||||
|
||||
/* Set the panel color */
|
||||
self~SetPanelColor(new_win, clrset)
|
||||
|
||||
/* Display title */
|
||||
new_win~attron(new_win~A_BOLD)
|
||||
new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
|
||||
new_win~attroff(new_win~A_BOLD)
|
||||
new_win~refresh
|
||||
if with_box = .true then DO
|
||||
new_win~box(0,0)
|
||||
new_win~refresh
|
||||
END
|
||||
return new_win
|
||||
|
||||
::METHOD SetPanelColor
|
||||
use arg panel, clrset
|
||||
|
||||
panel~start_color()
|
||||
SELECT
|
||||
/* white text on blue background */
|
||||
when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
|
||||
/* white text on grey background */
|
||||
when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
|
||||
/* yellow text on red background */
|
||||
when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
|
||||
/* black text on white background */
|
||||
when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
|
||||
/* black text on green background */
|
||||
when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
|
||||
/* black text on cyan background */
|
||||
when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
|
||||
/* Default to white text on black background */
|
||||
OTHERWISE DO
|
||||
clrset=0
|
||||
panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
|
||||
END
|
||||
END
|
||||
panel~bkgd(panel~color_pair(clrset+1))
|
||||
panel~refresh
|
||||
|
||||
::method setupMainMenu
|
||||
expose mainMenu win menuwin menuItems menu_keys
|
||||
use arg win
|
||||
|
||||
max_y = win~lines
|
||||
max_x = win~cols
|
||||
|
||||
menu_height = 21
|
||||
menu_width = 40
|
||||
start_y = (max_y - menu_height) % 2
|
||||
start_x = (max_x - menu_width) % 2
|
||||
clrset=1
|
||||
title = "Rexx Address Book"
|
||||
with_box = .true
|
||||
|
||||
menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
|
||||
menuItems = .array~of("[A]dd Contact", "[R]emove Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "E[X]it")
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
/* Display menu items */
|
||||
selected = 1
|
||||
self~DrawMenu(menuwin, menuItems, selected, win)
|
||||
/* menuwin~mvaddstr(menu_height - 2, 3, "Type 'X' to exit") */
|
||||
menuwin~refresh
|
||||
RETURN
|
||||
|
||||
::method DrawMenu
|
||||
expose win menuItems selected mainwin
|
||||
use arg win, items, selected, mainwin
|
||||
|
||||
do i = 1 to items~items
|
||||
if i = selected then do
|
||||
win~attron(mainwin~COLOR_PAIR(2))
|
||||
win~attron(mainwin~A_BOLD)
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
win~attroff(mainwin~COLOR_PAIR(2))
|
||||
win~attroff(mainwin~A_BOLD)
|
||||
end
|
||||
else do
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
end
|
||||
end
|
||||
win~refresh
|
||||
return
|
||||
|
||||
/** MAIN LOOP **/
|
||||
::method mainLoop
|
||||
expose win mainMenu menuwin
|
||||
|
||||
running = .true
|
||||
|
||||
do while running
|
||||
menuwin~refresh
|
||||
key = win~getch
|
||||
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
select
|
||||
when key = win~KEY_UP then do
|
||||
if selected > 1 then selected = selected - 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = win~KEY_DOWN then do
|
||||
if selected < menuItems~items then selected = selected + 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
|
||||
menuwin~refresh
|
||||
call ProcessSelection menu_keys[selected], home
|
||||
return
|
||||
end
|
||||
when key = D2C(88) | key = D2C(120) | key = "x" | key = C2D("x") then do
|
||||
menuwin~endwin
|
||||
.environment['STOPNOW'] = 1
|
||||
RETURN
|
||||
end
|
||||
otherwise do
|
||||
if datatype(key) = 'CHAR' then do
|
||||
key = lower(key)
|
||||
pos = self~findInArray(menu_keys, key)
|
||||
if pos > 0 then do
|
||||
menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
|
||||
menuwin~refresh
|
||||
call SysWait 0.5
|
||||
self~ProcessSelection(menuwin, key)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end /* do while running */
|
||||
return
|
||||
|
||||
/* Process selection */
|
||||
::METHOD ProcessSelection
|
||||
use arg menuwin, key_char
|
||||
select
|
||||
when key_char = 'a' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the ADD panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'r' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the REMOVE panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'e' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the EDIT panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 's' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the SEARCH panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'l' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the LIST panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
otherwise nop
|
||||
end
|
||||
return
|
||||
|
||||
::METHOD findInArray
|
||||
use arg array, item
|
||||
do i = 1 to array~items
|
||||
if array[i] = item then return i
|
||||
end
|
||||
return 0 /* Not found */
|
||||
|
||||
::method cleanup
|
||||
expose win
|
||||
/* Clean up ncurses */
|
||||
win~endwin
|
||||
return
|
11
app/utils.rex
Normal file
11
app/utils.rex
Normal file
@ -0,0 +1,11 @@
|
||||
::ROUTINE SysWait PUBLIC
|
||||
use arg seconds
|
||||
address system 'sleep' seconds
|
||||
return
|
||||
|
||||
|
||||
/* Handle program termination */
|
||||
::ROUTINE ProgramHalt
|
||||
signal off halt
|
||||
Say "PROGRAM TERMINATED AT THE KEYBOARD"
|
||||
exit 0
|
BIN
db/test_contacts.sqlite
Normal file
BIN
db/test_contacts.sqlite
Normal file
Binary file not shown.
326
tests/test_appdb.rexx
Executable file
326
tests/test_appdb.rexx
Executable file
@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env rexx
|
||||
/***************************************************************************
|
||||
* Rexx Address Book - Database Test Script *
|
||||
* *
|
||||
* This script tests the database functionality without the ncurses UI. *
|
||||
* It steps through creating, reading, updating, and deleting contacts *
|
||||
* along with their associated data. *
|
||||
***************************************************************************/
|
||||
|
||||
/* Setup environment */
|
||||
.local~home = SysGetpwnam("gmgauthier", "d")
|
||||
.local~projectRoot = .home||"/Projects/rexx-address-book"
|
||||
.local~appPkg = .projectRoot||"/app"
|
||||
.local~dbPath = .projectRoot||"/db/test_contacts.sqlite" /* Use a test database */
|
||||
|
||||
|
||||
tests = .TestSuite~new()
|
||||
|
||||
DO
|
||||
tests~TestCreateContact
|
||||
tests~TestGetContact
|
||||
tests~TestGetAllContacts
|
||||
tests~TestAddPhoneNumber
|
||||
tests~TestAddEmailAddress
|
||||
tests~TestAddRealAddress
|
||||
tests~TestFullDetailRetrieval
|
||||
tests~TestUpdateContact
|
||||
END
|
||||
|
||||
tests~tearDown
|
||||
|
||||
exit 0
|
||||
|
||||
::requires 'app/appdb.cls'
|
||||
::requires 'ooSQLite.cls'
|
||||
::requires "rxunixsys" LIBRARY
|
||||
::requires 'ncurses.cls'
|
||||
::requires 'app/utils.rex'
|
||||
|
||||
|
||||
::CLASS TestSuite
|
||||
::METHOD init
|
||||
self~setUp()
|
||||
|
||||
::METHOD setUp
|
||||
expose db contactDict
|
||||
if SysFileExists(.dbPath) then do
|
||||
call SysFileDelete .dbPath
|
||||
say "Deleted existing test database."
|
||||
end
|
||||
say "Initializing test database at:" .dbPath
|
||||
db = .AddressBookDB~new()
|
||||
self~setupFixtureData()
|
||||
|
||||
::METHOD tearDown
|
||||
expose db
|
||||
db~closeDb()
|
||||
say "Test completed!"
|
||||
|
||||
::METHOD setupFixtureData
|
||||
expose db contactDict addressDict emailArray phoneArray
|
||||
|
||||
emailArray = .Array~new()
|
||||
emailDict1 = .Directory~new()
|
||||
emailDict1~Type = "HOME"
|
||||
emailDict1~Number = "john.doe@home.com"
|
||||
emailArray~append(emailDict1)
|
||||
emailDict2 = .Directory~new()
|
||||
emailDict2~Type = "WORK"
|
||||
emailDict2~Number = "john.doe@work.com"
|
||||
emailArray~append(emailDict2)
|
||||
|
||||
phoneArray = .Array~new()
|
||||
phoneDict1 = .Directory~new()
|
||||
phoneDict1~Type = "HOME"
|
||||
phoneDict1~Number = "123-456-7890"
|
||||
phoneArray~append(phoneDict1)
|
||||
phoneDict2 = .Directory~new()
|
||||
phoneDict2~Type = "WORK"
|
||||
phoneDict2~Number = "987-654-3210"
|
||||
phoneArray~append(phoneDict2)
|
||||
|
||||
contactDict = .Directory~new()
|
||||
contactDict~firstName = "John"
|
||||
contactDict~lastName = "Doe"
|
||||
|
||||
|
||||
::METHOD TestCreateContact
|
||||
expose db contactDict
|
||||
/* Test contact creation */
|
||||
say ""
|
||||
say "=== TESTING CONTACT CREATION ==="
|
||||
say "Creating contact:" contactDict~firstName contactDict~lastName
|
||||
contactId = db~addContact(contactDict)
|
||||
say "Contact created with ID:" contactId
|
||||
|
||||
::METHOD TestGetContact
|
||||
expose db contactDict
|
||||
/* Test retrieving contact */
|
||||
say ""
|
||||
say "=== TESTING CONTACT RETRIEVAL ==="
|
||||
contact = db~getContact(1)
|
||||
if contact \= .nil then do
|
||||
say "Contact retrieved successfully:"
|
||||
say " ID:" contact['ID']
|
||||
say " Name:" contact['FIRST_NAME'] contact['LAST_NAME']
|
||||
say " Created at:" contact['CREATED_AT']
|
||||
end
|
||||
else do
|
||||
say "ERROR: Failed to retrieve contact!"
|
||||
end
|
||||
|
||||
::METHOD TestGetAllContacts
|
||||
expose db contactDict
|
||||
/* Get all contacts */
|
||||
say ""
|
||||
say "=== TESTING GET ALL CONTACTS ==="
|
||||
/* add some extras */
|
||||
db~addContact(contactDict)
|
||||
db~addContact(contactDict)
|
||||
|
||||
allContacts = db~getAllContacts()
|
||||
say "Total contacts in database:" allContacts~items()
|
||||
do contact over allContacts
|
||||
say " " contact['FIRST_NAME'] contact['LAST_NAME'] "(ID:" contact['ID']")"
|
||||
end
|
||||
|
||||
::METHOD TestAddPhoneNumber
|
||||
expose db contactDict
|
||||
/* Test adding phone numbers */
|
||||
say ""
|
||||
say "=== TESTING PHONE NUMBER ADDITION ==="
|
||||
Say "Adding phone numbers for contact id: 1"
|
||||
|
||||
say "Adding home phone number..."
|
||||
homePhoneId = db~addPhoneNumber(1, "Home", "555-123-4567")
|
||||
say "Added phone with ID:" homePhoneId
|
||||
|
||||
say "Adding mobile phone number..."
|
||||
mobilePhoneId = db~addPhoneNumber(1, "Mobile", "555-987-6543")
|
||||
say "Added phone with ID:" mobilePhoneId
|
||||
|
||||
::METHOD TestAddEmailAddress
|
||||
expose db contactDict
|
||||
/* Test adding email addresses */
|
||||
say ""
|
||||
say "=== TESTING EMAIL ADDITION ==="
|
||||
Say "Adding email addresses for contact id: 1"
|
||||
|
||||
say "Adding personal email..."
|
||||
personalEmailId = db~addEmailAddress(1, "Personal", "john.doe@personal.example")
|
||||
say "Added email with ID:" personalEmailId
|
||||
|
||||
say "Adding work email..."
|
||||
workEmailId = db~addEmailAddress(1, "Work", "john.doe@work.example")
|
||||
say "Added email with ID:" workEmailId
|
||||
|
||||
::METHOD TestAddRealAddress
|
||||
expose db contactDict
|
||||
/* Test adding physical address */
|
||||
say ""
|
||||
say "=== TESTING ADDRESS ADDITION ==="
|
||||
Say "Adding real addresses for contact id: 1"
|
||||
|
||||
say "Adding home address..."
|
||||
homeAddressId = db~addRealAddress(1, "Home", "123 Main St", "Anytown", "NY", "12345", "USA")
|
||||
say "Added address with ID:" homeAddressId
|
||||
|
||||
::METHOD TestFullDetailRetrieval
|
||||
expose db ContactDict
|
||||
/* Read the complete contact details */
|
||||
say ""
|
||||
say "=== TESTING COMPLETE CONTACT RETRIEVAL ==="
|
||||
contact = db~getContact(1)
|
||||
if contact \= .nil then do
|
||||
say "Contact retrieved successfully:"
|
||||
say " ID:" contact['ID']
|
||||
say " Name:" contact['FIRST_NAME'] contact['LAST_NAME']
|
||||
say " Created at:" contact['CREATED_AT']
|
||||
|
||||
phones = db~getPhoneNumbers(1)
|
||||
say "Phone numbers:"
|
||||
do phone over phones
|
||||
say " " phone['TYPE']":" phone['NUMBER'] "(ID:" phone['ID']")"
|
||||
end
|
||||
|
||||
emails = db~getEmailAddresses(1)
|
||||
say "Email addresses:"
|
||||
do email over emails
|
||||
say " " email['TYPE']":" email['EMAIL'] "(ID:" email['ID']")"
|
||||
end
|
||||
|
||||
addresses = db~getRealAddresses(1)
|
||||
say "Addresses:"
|
||||
do address over addresses
|
||||
say " " address['TYPE']" (ID: "address['ID']"):"
|
||||
say " Street:" address['STREET']
|
||||
say " City:" address['CITY']
|
||||
say " State:" address['STATE']
|
||||
say " Postal code:" address['POSTAL_CODE']
|
||||
say " Country:" address['COUNTRY']
|
||||
end
|
||||
end
|
||||
else do
|
||||
say "ERROR: Failed to retrieve complete contact details!"
|
||||
end
|
||||
|
||||
::METHOD TestUpdateContact
|
||||
expose db ContactDict
|
||||
/* Test updating contact information */
|
||||
say ""
|
||||
say "=== TESTING CONTACT UPDATE ==="
|
||||
say "Updating contact name..."
|
||||
updatedFirstName = "Jonathan"
|
||||
updatedLastName = "Dorian"
|
||||
result = db~updateContact(1, updatedFirstName, updatedLastName)
|
||||
say "Update successful:" result
|
||||
|
||||
say "Updating home phone number..."
|
||||
result = db~updatePhoneNumber(1, "Home", "555-111-2222")
|
||||
say "Update successful:" result
|
||||
|
||||
say "Updating work email..."
|
||||
result = db~updateEmailAddress(2, "Work", "jonathan.dorian@hospital.example")
|
||||
say "Update successful:" result
|
||||
|
||||
say "Updating home address..."
|
||||
result = db~updateRealAddress(1, "Home", "456 Oak Ave", "Springfield", "IL", "54321", "USA")
|
||||
say "Update successful:" result
|
||||
|
||||
/* Read the updated contact details */
|
||||
say ""
|
||||
say "=== TESTING UPDATED CONTACT RETRIEVAL ==="
|
||||
contact = db~getContact(1)
|
||||
if contact \= .nil then do
|
||||
say "Updated contact:"
|
||||
say " ID:" contact['ID']
|
||||
say " Name:" contact['FIRST_NAME'] contact['LAST_NAME']
|
||||
say " Created at:" contact['CREATED_AT']
|
||||
|
||||
say "Updated phone numbers:"
|
||||
phones = db~getPhoneNumbers(1)
|
||||
do phone over phones
|
||||
say " " phone['TYPE']":" phone['NUMBER'] "(ID:" phone['ID']")"
|
||||
end
|
||||
|
||||
emails = db~getEmailAddresses(1)
|
||||
say "Email addresses:"
|
||||
do email over emails
|
||||
say " " email['TYPE']":" email['EMAIL'] "(ID:" email['ID']")"
|
||||
end
|
||||
|
||||
addresses = db~getRealAddresses(1)
|
||||
say "Addresses:"
|
||||
do address over addresses
|
||||
say " " address['TYPE']" (ID: "address['ID']"):"
|
||||
say " Street:" address['STREET']
|
||||
say " City:" address['CITY']
|
||||
say " State:" address['STATE']
|
||||
say " Postal code:" address['POSTAL_CODE']
|
||||
say " Country:" address['COUNTRY']
|
||||
end
|
||||
end
|
||||
else do
|
||||
say "ERROR: Failed to retrieve updated contact details!"
|
||||
end
|
||||
|
||||
::METHOD TestDeletion
|
||||
say ""
|
||||
say "=== TESTING DELETION ==="
|
||||
|
||||
/* Test deleting an email */
|
||||
say "Deleting personal email..."
|
||||
result = db~deleteEmailAddress(personalEmailId)
|
||||
say "Deletion successful:" result
|
||||
|
||||
/* Get contact to verify deletion */
|
||||
contact = db~getContact(contactId)
|
||||
say "Email addresses after deletion:"
|
||||
do email over contact['emails']
|
||||
say " " email['type']":" email['email'] "(ID:" email['id']")"
|
||||
end
|
||||
|
||||
::METHOD OtherStuff
|
||||
/* Test deleting the entire contact */
|
||||
say "Deleting entire contact..."
|
||||
result = db~deleteContact(contactId)
|
||||
say "Deletion successful:" result
|
||||
|
||||
/* Try to retrieve the deleted contact */
|
||||
contact = db~getContact(contactId)
|
||||
if contact = .nil then
|
||||
say "Contact successfully deleted - could not retrieve contact with ID" contactId
|
||||
else
|
||||
say "ERROR: Contact was not properly deleted!"
|
||||
|
||||
/* Test searching functionality */
|
||||
say ""
|
||||
say "=== TESTING SEARCH FUNCTIONALITY ==="
|
||||
|
||||
/* Add multiple contacts for search testing */
|
||||
say "Adding test contacts for search..."
|
||||
db~addContact("Jane", "Smith")
|
||||
db~addContact("John", "Johnson")
|
||||
db~addContact("Bob", "Smith")
|
||||
db~addContact("Sarah", "Williams")
|
||||
|
||||
/* Search for contacts */
|
||||
say "Searching for 'Smith'..."
|
||||
results = db~searchContacts("Smith")
|
||||
say "Found" results~items() "contacts:"
|
||||
do contact over results
|
||||
say " " contact['firstName'] contact['lastName'] "(ID:" contact['id']")"
|
||||
end
|
||||
|
||||
say "Searching for 'John'..."
|
||||
results = db~searchContacts("John")
|
||||
say "Found" results~items() "contacts:"
|
||||
do contact over results
|
||||
say " " contact['firstName'] contact['lastName'] "(ID:" contact['id']")"
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user