190 lines
6.5 KiB
Python
190 lines
6.5 KiB
Python
# This file is part of sbs
|
|
#
|
|
# Copyright (C) 2025 Steve Milner
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# Generated by Gemini 2.5 Flash
|
|
|
|
import pytest
|
|
import csv
|
|
|
|
from unittest.mock import patch, mock_open
|
|
|
|
from sbs.ds.card import Card
|
|
from sbs.ds.csvexp import csvexp
|
|
|
|
|
|
def test_csvexp_reads_single_card():
|
|
"""
|
|
Test that csvexp correctly reads a single row from a CSV and yields one Card object.
|
|
"""
|
|
# Simulate CSV content as a string
|
|
csv_content = (
|
|
"Issue key,Assignee,Summary,Description,Custom field (Story Points)\n"
|
|
"PROJ-1,John Doe,Task 1,Desc 1,5\n"
|
|
)
|
|
|
|
# Use patch.object to mock csv.DictReader's __iter__ method
|
|
# and mock_open to simulate file reading
|
|
with patch("builtins.open", mock_open(read_data=csv_content)) as mock_file:
|
|
# Mock csv.DictReader. We'll manually specify its behavior.
|
|
# When csv.DictReader is called, it returns an iterable.
|
|
# We need to ensure that iterable provides the dictionary rows.
|
|
mock_reader_instance = iter(
|
|
[
|
|
{
|
|
"Issue key": "PROJ-1",
|
|
"Assignee": "John Doe",
|
|
"Summary": "Task 1",
|
|
"Description": "Desc 1",
|
|
"Custom field (Story Points)": "5",
|
|
}
|
|
]
|
|
)
|
|
with patch(
|
|
"csv.DictReader", return_value=mock_reader_instance
|
|
) as mock_dict_reader:
|
|
cards = list(csvexp("dummy.csv"))
|
|
|
|
# Assertions
|
|
mock_file.assert_called_once_with(
|
|
"dummy.csv", "r"
|
|
) # Check if open was called
|
|
mock_dict_reader.assert_called_once_with(
|
|
mock_file()
|
|
) # Check if DictReader was called with the file handle
|
|
|
|
assert len(cards) == 1
|
|
card = cards[0]
|
|
assert isinstance(card, Card)
|
|
assert card.key == "PROJ-1"
|
|
assert card.assignee == "John Doe"
|
|
assert card.summary == "Task 1"
|
|
assert card.description == "Desc 1"
|
|
assert card.story_points == "5" # Story points are read as string from CSV
|
|
|
|
|
|
def test_csvexp_reads_multiple_cards():
|
|
"""
|
|
Test that csvexp correctly reads multiple rows from a CSV and yields multiple Card objects.
|
|
"""
|
|
csv_content = (
|
|
"Issue key,Assignee,Summary,Description,Custom field (Story Points)\n"
|
|
"PROJ-1,John Doe,Task 1,Desc 1,5\n"
|
|
"PROJ-2,Jane Smith,Task 2,Desc 2,3\n"
|
|
"PROJ-3,Peter Jones,Task 3,Desc 3,8\n"
|
|
)
|
|
|
|
with patch("builtins.open", mock_open(read_data=csv_content)) as mock_file:
|
|
mock_reader_instance = iter(
|
|
[
|
|
{
|
|
"Issue key": "PROJ-1",
|
|
"Assignee": "John Doe",
|
|
"Summary": "Task 1",
|
|
"Description": "Desc 1",
|
|
"Custom field (Story Points)": "5",
|
|
},
|
|
{
|
|
"Issue key": "PROJ-2",
|
|
"Assignee": "Jane Smith",
|
|
"Summary": "Task 2",
|
|
"Description": "Desc 2",
|
|
"Custom field (Story Points)": "3",
|
|
},
|
|
{
|
|
"Issue key": "PROJ-3",
|
|
"Assignee": "Peter Jones",
|
|
"Summary": "Task 3",
|
|
"Description": "Desc 3",
|
|
"Custom field (Story Points)": "8",
|
|
},
|
|
]
|
|
)
|
|
with patch("csv.DictReader", return_value=mock_reader_instance):
|
|
cards = list(csvexp("multiple_cards.csv"))
|
|
|
|
assert len(cards) == 3
|
|
|
|
assert cards[0].key == "PROJ-1"
|
|
assert cards[1].assignee == "Jane Smith"
|
|
assert cards[2].story_points == "8"
|
|
|
|
|
|
def test_csvexp_handles_empty_csv():
|
|
"""
|
|
Test that csvexp returns an empty list when the CSV file is empty (only header or no data).
|
|
"""
|
|
csv_content = "Issue key,Assignee,Summary,Description,Custom field (Story Points)\n"
|
|
|
|
with patch("builtins.open", mock_open(read_data=csv_content)) as mock_file:
|
|
# csv.DictReader on an empty file (after header) will yield nothing
|
|
mock_reader_instance = iter([])
|
|
with patch("csv.DictReader", return_value=mock_reader_instance):
|
|
cards = list(csvexp("empty.csv"))
|
|
assert len(cards) == 0
|
|
|
|
|
|
def test_csvexp_handles_missing_file():
|
|
"""
|
|
Test that csvexp raises a FileNotFoundError if the specified CSV file does not exist.
|
|
"""
|
|
with patch("builtins.open", side_effect=FileNotFoundError):
|
|
with pytest.raises(FileNotFoundError):
|
|
list(csvexp("non_existent.csv"))
|
|
|
|
|
|
def test_csvexp_handles_missing_column():
|
|
"""
|
|
Test that csvexp raises a KeyError if a required column is missing from the CSV.
|
|
"""
|
|
csv_content = (
|
|
"Issue key,Assignee,Summary,Description,Missing Column\n"
|
|
"PROJ-1,John Doe,Task 1,Desc 1,5\n"
|
|
)
|
|
|
|
with patch("builtins.open", mock_open(read_data=csv_content)) as mock_file:
|
|
# Simulate DictReader providing a row missing a key
|
|
mock_reader_instance = iter(
|
|
[
|
|
{
|
|
"Issue key": "PROJ-1",
|
|
"Assignee": "John Doe",
|
|
"Summary": "Task 1",
|
|
"Description": "Desc 1",
|
|
"Missing Column": "5",
|
|
} # Missing "Custom field (Story Points)"
|
|
]
|
|
)
|
|
with patch("csv.DictReader", return_value=mock_reader_instance):
|
|
with pytest.raises(KeyError):
|
|
list(csvexp("missing_column.csv"))
|
|
|
|
|
|
def test_csvexp_ignore_parameter():
|
|
"""
|
|
Test that the `ignore` parameter is correctly accepted and does not interfere.
|
|
"""
|
|
csv_content = (
|
|
"Issue key,Assignee,Summary,Description,Custom field (Story Points)\n"
|
|
"PROJ-1,John Doe,Task 1,Desc 1,5\n"
|
|
)
|
|
|
|
with patch("builtins.open", mock_open(read_data=csv_content)) as mock_file:
|
|
mock_reader_instance = iter(
|
|
[
|
|
{
|
|
"Issue key": "PROJ-1",
|
|
"Assignee": "John Doe",
|
|
"Summary": "Task 1",
|
|
"Description": "Desc 1",
|
|
"Custom field (Story Points)": "5",
|
|
}
|
|
]
|
|
)
|
|
with patch("csv.DictReader", return_value=mock_reader_instance):
|
|
# Pass an extra keyword argument that should be ignored
|
|
cards = list(csvexp("dummy.csv", extra_param="value"))
|
|
|
|
assert len(cards) == 1
|
|
assert cards[0].key == "PROJ-1"
|