Skip to content

Commit

Permalink
Merge pull request #495 from cmoussa1/fix.edit-user.projects
Browse files Browse the repository at this point in the history
projects: fix unit tests for project subcommands, `--projects` reset capability
  • Loading branch information
mergify[bot] authored Oct 8, 2024
2 parents b83498e + ff42008 commit b0bd954
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 83 deletions.
24 changes: 24 additions & 0 deletions src/bindings/python/fluxacct/accounting/user_subcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,26 @@ def clear_queues(conn, username, bank=None):
return 0


def clear_projects(conn, username, bank=None):
update_stmt = "UPDATE association_table SET projects='*' WHERE username=?"
if bank is None:
conn.execute(update_stmt, (username,))
else:
update_stmt += " AND bank=?"
conn.execute(
update_stmt,
(
username,
bank,
),
)

update_mod_time(conn, username, bank)
conn.commit()

return 0


###############################################################
# #
# Subcommand Functions #
Expand Down Expand Up @@ -480,6 +500,10 @@ def edit_user(
except ValueError as bad_queue:
raise ValueError(f"queue {bad_queue} does not exist in queue_table")
if field == "projects":
if str(params[field]) == "-1":
# reset the association's projects list to just "*"
clear_projects(conn, username, bank)
return 0
try:
params[field] = validate_project(conn, params[field])
except ValueError as bad_project:
Expand Down
192 changes: 109 additions & 83 deletions t/python/t1005_project_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,108 +9,134 @@
#
# SPDX-License-Identifier: LGPL-3.0
###############################################################
import unittest
import os
import sqlite3

from fluxacct.accounting import create_db as c
from fluxacct.accounting import user_subcommands as u
from fluxacct.accounting import bank_subcommands as b
from fluxacct.accounting import project_subcommands as p


class TestAccountingCLI(unittest.TestCase):
@classmethod
def setUpClass(self):
# create test accounting database
c.create_db("TestProjectSubcommands.db")
global acct_conn
global cur

acct_conn = sqlite3.connect("TestProjectSubcommands.db")
cur = acct_conn.cursor()

# add a valid project to project_table
def test_01_add_valid_projects(self):
p.add_project(acct_conn, project="project_1")
cur.execute("SELECT * FROM project_table WHERE project='project_1'")
rows = cur.fetchall()

self.assertEqual(len(rows), 1)

# let's make sure if we try to add it a second time,
# it fails gracefully
def test_02_add_dup_project(self):
with self.assertRaises(sqlite3.IntegrityError):
p.add_project(acct_conn, project="project_1")

# remove a project currently in the project_table
def test_03_delete_project(self):
p.delete_project(acct_conn, project="project_1")
cur.execute("SELECT * FROM project_table WHERE project='project_1'")
rows = cur.fetchall()

self.assertEqual(len(rows), 0)

# add a user to the accounting DB without specifying a default project
def test_04_default_project_unspecified(self):
b.add_bank(acct_conn, bank="A", shares=1)
u.add_user(acct_conn, username="user5001", uid=5001, bank="A")
cur.execute(
"SELECT default_project FROM association_table WHERE username='user5001' AND bank='A'"
)
rows = cur.fetchall()

###############################################################
# #
# Helper Functions #
# #
###############################################################
self.assertEqual(rows[0][0], "*")

# add a user to the accounting DB by specifying a default project
def test_05_default_project_specified(self):
p.add_project(acct_conn, project="project_1")
u.add_user(
acct_conn, username="user5002", uid=5002, bank="A", projects="project_1"
)
cur.execute(
"SELECT default_project FROM association_table WHERE username='user5002' AND bank='A'"
)
rows = cur.fetchall()

# check if project already exists and is active in project_table;
# if so, return True
def project_is_active(cur, project):
cur.execute(
"SELECT * FROM project_table WHERE project=?",
(project,),
)
project_exists = cur.fetchall()
if len(project_exists) > 0:
return True
self.assertEqual(rows[0][0], "project_1")

return False
# make sure "*" is also added to the user's project list
cur.execute(
"SELECT projects FROM association_table WHERE username='user5002' AND bank='A'"
)
rows = cur.fetchall()

self.assertEqual(rows[0][0], "project_1,*")

###############################################################
# #
# Subcommand Functions #
# #
###############################################################
# edit a user's default project
def test_06_edit_default_project(self):
u.edit_user(acct_conn, username="user5002", bank="A", default_project="*")
cur.execute(
"SELECT default_project FROM association_table WHERE username='user5002' AND bank='A'"
)
rows = cur.fetchall()

self.assertEqual(rows[0][0], "*")

def view_project(conn, project):
cur = conn.cursor()
try:
# get the information pertaining to a project in the DB
cur.execute("SELECT * FROM project_table where project=?", (project,))
result = cur.fetchall()
headers = [description[0] for description in cur.description]
project_str = ""
if not result:
raise ValueError(f"project {project} not found in project_table")

for header in headers:
project_str += header.ljust(18)
project_str += "\n"
for row in result:
for col in list(row):
project_str += str(col).ljust(18)
project_str += "\n"

return project_str
except sqlite3.OperationalError as exc:
raise sqlite3.OperationalError(f"an sqlite3.OperationalError occurred: {exc}")


def add_project(conn, project):
cur = conn.cursor()

if project_is_active(cur, project):
raise sqlite3.IntegrityError(
f"project {project} already exists in project_table"
# make sure projects list gets edited correctly
u.edit_user(acct_conn, username="user5002", bank="A", projects="project_1")
cur.execute(
"SELECT projects FROM association_table WHERE username='user5002' AND bank='A'"
)
rows = cur.fetchall()

try:
insert_stmt = "INSERT INTO project_table (project) VALUES (?)"
conn.execute(
insert_stmt,
(project,),
)
self.assertEqual(rows[0][0], "project_1,*")

# editing a user's project list with a bad project name should raise a ValueError
def test_07_edit_projects_list_bad_name(self):
with self.assertRaises(ValueError):
u.edit_user(acct_conn, username="user5002", bank="A", projects="foo")

conn.commit()
# trying to view a project that does not exist should raise a ValueError
def test_08_view_project_nonexistent(self):
with self.assertRaises(ValueError):
p.view_project(acct_conn, "foo")

return 0
# make sure entry is unique
except sqlite3.IntegrityError:
raise sqlite3.IntegrityError(
f"project {project} already exists in project_table"
# reset the lists of projects for an association
def test_09_reset_projects_for_association(self):
u.edit_user(acct_conn, username="user5002", projects=-1)
cur.execute(
"SELECT projects, default_project FROM association_table WHERE username='user5002' AND bank='A'"
)
rows = cur.fetchall()

print(rows)

# remove database and log file
@classmethod
def tearDownClass(self):
acct_conn.close()
os.remove("TestProjectSubcommands.db")

def delete_project(conn, project):
cursor = conn.cursor()

# look for any rows in the association_table that reference this project
select_stmt = "SELECT * FROM association_table WHERE projects LIKE ?"
cursor.execute(select_stmt, ("%" + project + "%",))
result = cursor.fetchall()
warning_stmt = (
"WARNING: user(s) in the assocation_table still "
"reference this project. Make sure to edit user rows to "
"account for this deleted project."
)
def suite():
suite = unittest.TestSuite()

delete_stmt = "DELETE FROM project_table WHERE project=?"
cursor.execute(delete_stmt, (project,))
return suite

conn.commit()

# if len(rows) > 0, this means that at least one association in the
# association_table references this project. If this is the case,
# return the warning message after deleting the project.
if len(result) > 0:
return warning_stmt
if __name__ == "__main__":
from pycotap import TAPTestRunner

return 0
unittest.main(testRunner=TAPTestRunner())
6 changes: 6 additions & 0 deletions t/t1025-flux-account-projects.t
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ test_expect_success 'edit the default project of a user' '
grep -f projects_list.expected edited_default_project.test
'

test_expect_success 'reset the projects list for an association' '
flux account edit-user user5018 --projects=-1 &&
flux account view-user user5018 --json > user5018.json &&
grep "\"projects\": \"*\"" user5018.json
'

test_expect_success 'remove flux-accounting DB' '
rm $(pwd)/FluxAccountingTest.db
'
Expand Down

0 comments on commit b0bd954

Please sign in to comment.