Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions application/cmd/cre_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ def register_standard(
# calculate gap analysis
populate_neo4j_db(db_connection_str)
jobs = []
pending_stadards = collection.standards()
for standard_name in pending_stadards:
pending_standards = collection.standards()
for standard_name in pending_standards:
if standard_name == importing_name:
continue

Expand All @@ -293,9 +293,9 @@ def register_standard(
jobs.append(forward_job)
except exceptions.NoSuchJobError as nje:
logger.error(
f"Could not find gap analysis job for for {importing_name} and {standard_name} putting {standard_name} back in the queue"
f"Could not find gap analysis job for {importing_name} and {standard_name} putting {standard_name} back in the queue"
)
Comment on lines 295 to 297
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in log message: duplicated word for ("job for for …").

Copilot uses AI. Check for mistakes.
pending_stadards.append(standard_name)
pending_standards.append(standard_name)

Comment on lines 280 to 299
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code appends back into pending_standards while iterating over it. Mutating the list during iteration can lead to unexpected behavior (including repeatedly re-processing the same standard if the exception keeps happening). Consider using a separate retry list/queue (e.g., to_retry.append(...) then extend after the loop), or iterating over a snapshot copy.

Copilot uses AI. Check for mistakes.
bw_key = gap_analysis.make_resources_key([standard_name, importing_name])
if not collection.gap_analysis_exists(bw_key):
Expand All @@ -308,9 +308,9 @@ def register_standard(
jobs.append(backward_job)
except exceptions.NoSuchJobError as nje:
logger.error(
f"Could not find gap analysis job for for {importing_name} and {standard_name} putting {standard_name} back in the queue"
f"Could not find gap analysis job for {importing_name} and {standard_name} putting {standard_name} back in the queue"
)
Comment on lines 310 to 312
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in log message: duplicated word for ("job for for …").

Copilot uses AI. Check for mistakes.
pending_stadards.append(standard_name)
pending_standards.append(standard_name)
redis.wait_for_jobs(jobs)
Comment on lines 300 to 314
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code appends back into pending_standards while iterating over it. Mutating the list during iteration can lead to unexpected behavior (including repeatedly re-processing the same standard if the exception keeps happening). Consider using a separate retry list/queue (e.g., to_retry.append(...) then extend after the loop), or iterating over a snapshot copy.

Copilot uses AI. Check for mistakes.
conn.set(standard_hash, value="")

Expand Down
27 changes: 13 additions & 14 deletions application/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,23 +981,23 @@ def get_by_tags(self, tags: List[str]) -> List[cre_defs.Document]:
cre_where_clause.append(sqla.and_(CRE.tags.like("%{}%".format(tag))))

nodes = Node.query.filter(*nodes_where_clause).all() or []
for node in nodes:
node = self.get_nodes(
name=node.name,
section=node.section,
subsection=node.subsection,
version=node.version,
link=node.link,
ntype=node.ntype,
sectionID=node.section_id,
for db_node in nodes:
resolved = self.get_nodes(
name=db_node.name,
section=db_node.section,
subsection=db_node.subsection,
version=db_node.version,
link=db_node.link,
ntype=db_node.ntype,
sectionID=db_node.section_id,
)
if node:
documents.extend(node)
if resolved:
documents.extend(resolved)
else:
logger.fatal(
"db.get_node returned None for"
"get_nodes() returned no documents for "
"Node %s:%s:%s that exists, BUG!"
% (node.name, node.section, node.section_id)
% (db_node.name, db_node.section, db_node.section_id)
)
Comment on lines 983 to 1001
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is test coverage for get_by_tags() (see application/tests/db_test.py::test_get_by_tags), but it doesn’t exercise the failure mode fixed here (where get_nodes() returns an empty list for a DB row that matched the initial tag query). Please add a regression test that simulates get_nodes() returning [] for a matching Node row to ensure get_by_tags() doesn’t crash when logging the error path.

Copilot uses AI. Check for mistakes.

cres = CRE.query.filter(*cre_where_clause).all() or []
Expand Down Expand Up @@ -1582,7 +1582,6 @@ def add_node(
) -> Optional[Node]:
if not node:
raise ValueError(f"Node is None")
return None
dbnode = dbNodeFromNode(node)
if not dbnode:
logger.warning(f"{node} could not be transformed to a DB object")
Expand Down
20 changes: 20 additions & 0 deletions application/tests/db_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,26 @@ def test_get_by_tags(self) -> None:
self.assertEqual(self.collection.get_by_tags([]), [])
self.assertEqual(self.collection.get_by_tags(["this should not be a tag"]), [])

@patch("application.database.db.logger")
def test_get_by_tags_empty_nodes_regression(self, mock_logger) -> None:
"""
Simulate get_nodes returning an empty list to test the error path in get_by_tags.
See PR #837 regression fix.
"""
dbstandard = db.Node(
section="regression_section",
name="regression_name",
tags="regression_tag",
ntype=defs.Standard.__name__,
)
self.collection.session.add(dbstandard)
self.collection.session.commit()

with patch.object(self.collection, "get_nodes", return_value=[]):
res = self.collection.get_by_tags(["regression_tag"])
self.assertEqual(res, [])
mock_logger.fatal.assert_called_once()

def test_get_standards_names(self) -> None:
result = self.collection.get_node_names()
expected = [("Standard", "BarStand"), ("Standard", "Unlinked")]
Expand Down
6 changes: 3 additions & 3 deletions application/utils/gap_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def get_path_score(path):

if step["relationship"] == "CONTAINS":
penalty_type = f"CONTAINS_{get_relation_direction(step, previous_id)}"
pentalty = PENALTIES[penalty_type]
score += pentalty
step["score"] = pentalty
penalty = PENALTIES[penalty_type]
score += penalty
step["score"] = penalty
previous_id = get_next_id(step, previous_id)
return score

Expand Down
1 change: 0 additions & 1 deletion application/web/web_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from rq import job, exceptions

from application.utils import spreadsheet_parsers
from application.utils import oscal_utils, redis
from application.database import db
from application.cmd import cre_main
from application.defs import cre_defs as defs
Expand Down