ara-cli 0.1.9.50__py3-none-any.whl → 0.1.9.52__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ara_cli/__main__.py +3 -1
- ara_cli/analyse_artefacts.py +133 -0
- ara_cli/ara_command_action.py +91 -76
- ara_cli/ara_command_parser.py +5 -0
- ara_cli/ara_config.py +65 -38
- ara_cli/artefact_creator.py +3 -4
- ara_cli/artefact_lister.py +57 -48
- ara_cli/artefact_models/artefact_model.py +28 -14
- ara_cli/artefact_models/feature_artefact_model.py +4 -1
- ara_cli/artefact_reader.py +104 -63
- ara_cli/artefact_renamer.py +8 -8
- ara_cli/artefact_scan.py +46 -0
- ara_cli/file_classifier.py +10 -32
- ara_cli/prompt_extractor.py +10 -2
- ara_cli/tag_extractor.py +6 -16
- ara_cli/templates/specification_breakdown_files/template.concept.md +12 -14
- ara_cli/tests/test_ara_command_action.py +242 -108
- ara_cli/tests/test_artefact_lister.py +598 -171
- ara_cli/tests/test_artefact_reader.py +7 -76
- ara_cli/tests/test_artefact_scan.py +126 -0
- ara_cli/tests/test_file_classifier.py +69 -30
- ara_cli/tests/test_tag_extractor.py +42 -61
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/RECORD +28 -27
- ara_cli/artefact.py +0 -172
- ara_cli/tests/test_artefact.py +0 -304
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/top_level.txt +0 -0
|
@@ -1,85 +1,16 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from unittest.mock import patch
|
|
2
|
+
from unittest.mock import patch
|
|
3
3
|
from ara_cli.artefact_reader import ArtefactReader
|
|
4
|
-
from ara_cli.artefact import Artefact
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@pytest.mark.parametrize("artefact_name, classifier, is_valid, file_exists, expected_output", [
|
|
8
|
-
("artefact1", "valid_classifier", True, True, ("file content", "/valid_classifier/artefact1.valid_classifier")),
|
|
9
|
-
("artefact2", "invalid_classifier", False, True, None),
|
|
10
|
-
("artefact3", "valid_classifier", True, False, None),
|
|
11
|
-
])
|
|
12
|
-
def test_read_artefact(artefact_name, classifier, is_valid, file_exists, expected_output):
|
|
13
|
-
original_directory = "/original/directory"
|
|
14
|
-
|
|
15
|
-
with patch('os.getcwd', return_value=original_directory), \
|
|
16
|
-
patch('os.chdir') as mock_chdir, \
|
|
17
|
-
patch('ara_cli.directory_navigator.DirectoryNavigator.navigate_to_target'), \
|
|
18
|
-
patch('ara_cli.classifier.Classifier.is_valid_classifier', return_value=is_valid), \
|
|
19
|
-
patch('ara_cli.classifier.Classifier.get_sub_directory', return_value=f"/{classifier}"), \
|
|
20
|
-
patch('os.path.exists', return_value=file_exists), \
|
|
21
|
-
patch('builtins.open', mock_open(read_data="file content")) as mock_file:
|
|
22
|
-
|
|
23
|
-
if expected_output is None:
|
|
24
|
-
expected_content, expected_file_path = None, None
|
|
25
|
-
else:
|
|
26
|
-
expected_content, expected_file_path = expected_output
|
|
27
|
-
|
|
28
|
-
content, file_path = ArtefactReader.read_artefact(artefact_name, classifier)
|
|
29
|
-
|
|
30
|
-
# Check if the content and file path match expected output
|
|
31
|
-
assert content == expected_content
|
|
32
|
-
assert file_path == expected_file_path
|
|
33
|
-
|
|
34
|
-
# Check the directory was changed back to original
|
|
35
|
-
mock_chdir.assert_called_with(original_directory)
|
|
36
|
-
|
|
37
|
-
if not is_valid:
|
|
38
|
-
mock_file.assert_not_called()
|
|
39
|
-
elif not file_exists:
|
|
40
|
-
mock_file.assert_not_called()
|
|
41
|
-
else:
|
|
42
|
-
mock_file.assert_called_once_with(expected_file_path, 'r')
|
|
43
4
|
|
|
44
5
|
|
|
45
6
|
@pytest.mark.parametrize("artefact_content, artefact_titles, expected_output", [
|
|
46
|
-
("Contributes to: parent_name
|
|
47
|
-
("Contributes to parent_name
|
|
48
|
-
("Contributes to : parent_name
|
|
49
|
-
("No contribution information here.", ["
|
|
50
|
-
("Contributes to : parent_name NotListedTitle", ["
|
|
7
|
+
("Contributes to: parent_name Example", ["Example"], ("parent_name", "Example")),
|
|
8
|
+
("Contributes to parent_name Example", ["Example"], ("parent_name", "Example")),
|
|
9
|
+
("Contributes to : parent_name Feature", ["Example", "Feature"], ("parent_name", "Feature")),
|
|
10
|
+
("No contribution information here.", ["Example"], (None, None)),
|
|
11
|
+
("Contributes to : parent_name NotListedTitle", ["Example"], (None, None)),
|
|
51
12
|
])
|
|
52
13
|
def test_extract_parent_tree(artefact_content, artefact_titles, expected_output):
|
|
53
14
|
with patch('ara_cli.classifier.Classifier.artefact_titles', return_value=artefact_titles):
|
|
54
15
|
parent_name, parent_type = ArtefactReader.extract_parent_tree(artefact_content)
|
|
55
|
-
assert (parent_name, parent_type) == expected_output
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@pytest.mark.parametrize("artefact_name, classifier, artefact_content, artefact_parent, expected_calls", [
|
|
59
|
-
("artefact1", "classifier1", "content1", None, [("content1",)]),
|
|
60
|
-
("artefact2", "classifier2", "content2", Mock(name="parent_artefact", classifier="parent_classifier"),
|
|
61
|
-
[("content2",)]),
|
|
62
|
-
("artefact3", "classifier3", "content3", None, [("content3",)]),
|
|
63
|
-
])
|
|
64
|
-
def test_step_through_value_chain(artefact_name, classifier, artefact_content, artefact_parent, expected_calls):
|
|
65
|
-
artefact_mock = Mock(spec=Artefact)
|
|
66
|
-
artefact_mock.name = artefact_name
|
|
67
|
-
artefact_mock.parent = artefact_parent
|
|
68
|
-
|
|
69
|
-
artefact_by_classifier = {}
|
|
70
|
-
|
|
71
|
-
with patch('ara_cli.artefact_reader.ArtefactReader.read_artefact', return_value=(artefact_content, "file_path")), \
|
|
72
|
-
patch('ara_cli.artefact.Artefact.from_content', return_value=artefact_mock) as mock_from_content:
|
|
73
|
-
|
|
74
|
-
ArtefactReader.step_through_value_chain(artefact_name, classifier, artefact_by_classifier)
|
|
75
|
-
|
|
76
|
-
# Check if artefact was added to the artefacts_by_classifier
|
|
77
|
-
assert artefact_mock in artefact_by_classifier[classifier]
|
|
78
|
-
|
|
79
|
-
# Check if the recursive calls are made correctly
|
|
80
|
-
for call_args in expected_calls:
|
|
81
|
-
mock_from_content.assert_any_call(*call_args)
|
|
82
|
-
|
|
83
|
-
# Ensure no duplicate artefacts are added
|
|
84
|
-
ArtefactReader.step_through_value_chain(artefact_name, classifier, artefact_by_classifier)
|
|
85
|
-
assert len(artefact_by_classifier[classifier]) == 1
|
|
16
|
+
assert (parent_name, parent_type) == expected_output
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock, mock_open, call
|
|
3
|
+
from ara_cli.artefact_scan import check_file, find_invalid_files, show_results
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_check_file_valid():
|
|
8
|
+
mock_artefact_class = MagicMock()
|
|
9
|
+
mock_artefact_class.deserialize.return_value = None
|
|
10
|
+
|
|
11
|
+
with patch("builtins.open", mock_open(read_data="valid content")):
|
|
12
|
+
is_valid, reason = check_file("dummy_path", mock_artefact_class)
|
|
13
|
+
assert is_valid is True
|
|
14
|
+
assert reason is None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_check_file_value_error():
|
|
18
|
+
mock_artefact_class = MagicMock()
|
|
19
|
+
mock_artefact_class.deserialize.side_effect = ValueError("Value error")
|
|
20
|
+
|
|
21
|
+
with patch("builtins.open", mock_open(read_data="invalid content")):
|
|
22
|
+
is_valid, reason = check_file("dummy_path", mock_artefact_class)
|
|
23
|
+
assert is_valid is False
|
|
24
|
+
assert "Value error" in reason
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_check_file_assertion_error():
|
|
28
|
+
mock_artefact_class = MagicMock()
|
|
29
|
+
mock_artefact_class.deserialize.side_effect = AssertionError(
|
|
30
|
+
"Assertion error")
|
|
31
|
+
|
|
32
|
+
with patch("builtins.open", mock_open(read_data="invalid content")):
|
|
33
|
+
is_valid, reason = check_file("dummy_path", mock_artefact_class)
|
|
34
|
+
assert is_valid is False
|
|
35
|
+
assert "Assertion error" in reason
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_check_file_os_error():
|
|
39
|
+
mock_artefact_class = MagicMock()
|
|
40
|
+
|
|
41
|
+
with patch("builtins.open", side_effect=OSError("File not found")):
|
|
42
|
+
is_valid, reason = check_file("dummy_path", mock_artefact_class)
|
|
43
|
+
assert is_valid is False
|
|
44
|
+
assert "File error: File not found" in reason
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_check_file_unexpected_error():
|
|
48
|
+
mock_artefact_class = MagicMock()
|
|
49
|
+
mock_artefact_class.deserialize.side_effect = Exception("Unexpected error")
|
|
50
|
+
|
|
51
|
+
with patch("builtins.open", mock_open(read_data="content")):
|
|
52
|
+
is_valid, reason = check_file("dummy_path", mock_artefact_class)
|
|
53
|
+
assert is_valid is False
|
|
54
|
+
assert "Unexpected error: Exception('Unexpected error')" in reason
|
|
55
|
+
|
|
56
|
+
# Tests for find_invalid_files
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_find_invalid_files():
|
|
60
|
+
mock_artefact_class = MagicMock()
|
|
61
|
+
with patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping", {"test_classifier": mock_artefact_class}):
|
|
62
|
+
artefact_files = {
|
|
63
|
+
"test_classifier": [{"file_path": "file1.txt"}, {"file_path": "file2.txt"}, {"file_path": "templates/file3.txt"}]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
with patch("ara_cli.artefact_scan.check_file") as mock_check_file:
|
|
67
|
+
mock_check_file.side_effect = [
|
|
68
|
+
(True, None), # file1.txt
|
|
69
|
+
(False, "Invalid content") # file2.txt
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
invalid_files = find_invalid_files(
|
|
73
|
+
artefact_files, "test_classifier")
|
|
74
|
+
assert len(invalid_files) == 1
|
|
75
|
+
assert invalid_files[0] == ("file2.txt", "Invalid content")
|
|
76
|
+
mock_check_file.assert_has_calls([
|
|
77
|
+
call("file1.txt", mock_artefact_class),
|
|
78
|
+
call("file2.txt", mock_artefact_class)
|
|
79
|
+
], any_order=False)
|
|
80
|
+
|
|
81
|
+
# Tests for show_results
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_show_results_no_issues(capsys):
|
|
85
|
+
invalid_artefacts = {}
|
|
86
|
+
with patch("builtins.open", mock_open()) as m:
|
|
87
|
+
show_results(invalid_artefacts)
|
|
88
|
+
captured = capsys.readouterr()
|
|
89
|
+
assert captured.out == "All files are good!\n"
|
|
90
|
+
m.assert_called_once_with("incompatible_artefacts_report.md", "w")
|
|
91
|
+
handle = m()
|
|
92
|
+
handle.write.assert_has_calls([
|
|
93
|
+
call("# Artefact Check Report\n\n"),
|
|
94
|
+
call("No problems found.\n")
|
|
95
|
+
], any_order=False)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_show_results_with_issues(capsys):
|
|
99
|
+
invalid_artefacts = {
|
|
100
|
+
"classifier1": [("file1.txt", "reason1"), ("file2.txt", "reason2")],
|
|
101
|
+
"classifier2": [("file3.txt", "reason3")]
|
|
102
|
+
}
|
|
103
|
+
with patch("builtins.open", mock_open()) as m:
|
|
104
|
+
show_results(invalid_artefacts)
|
|
105
|
+
captured = capsys.readouterr()
|
|
106
|
+
expected_output = (
|
|
107
|
+
"\nIncompatible classifier1 Files:\n"
|
|
108
|
+
"\t- file1.txt\n"
|
|
109
|
+
"\t- file2.txt\n"
|
|
110
|
+
"\nIncompatible classifier2 Files:\n"
|
|
111
|
+
"\t- file3.txt\n"
|
|
112
|
+
)
|
|
113
|
+
assert captured.out == expected_output
|
|
114
|
+
m.assert_called_once_with("incompatible_artefacts_report.md", "w")
|
|
115
|
+
handle = m()
|
|
116
|
+
expected_writes = [
|
|
117
|
+
call("# Artefact Check Report\n\n"),
|
|
118
|
+
call("## classifier1\n"),
|
|
119
|
+
call("- `file1.txt`: reason1\n"),
|
|
120
|
+
call("- `file2.txt`: reason2\n"),
|
|
121
|
+
call("\n"),
|
|
122
|
+
call("## classifier2\n"),
|
|
123
|
+
call("- `file3.txt`: reason3\n"),
|
|
124
|
+
call("\n")
|
|
125
|
+
]
|
|
126
|
+
handle.write.assert_has_calls(expected_writes, any_order=False)
|
|
@@ -2,7 +2,8 @@ import pytest
|
|
|
2
2
|
from unittest.mock import MagicMock, patch, mock_open, call
|
|
3
3
|
from ara_cli.file_classifier import FileClassifier
|
|
4
4
|
from ara_cli.classifier import Classifier
|
|
5
|
-
from ara_cli.
|
|
5
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
6
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@pytest.fixture
|
|
@@ -22,12 +23,6 @@ def mock_get_artefact_title():
|
|
|
22
23
|
yield
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@pytest.fixture
|
|
26
|
-
def mock_artefact():
|
|
27
|
-
with patch.object(Artefact, 'from_content', side_effect=lambda content: Artefact(classifier='', name='', _file_path='')):
|
|
28
|
-
yield
|
|
29
|
-
|
|
30
|
-
|
|
31
26
|
def test_file_classifier_init(mock_file_system):
|
|
32
27
|
classifier = FileClassifier(mock_file_system)
|
|
33
28
|
assert classifier.file_system == mock_file_system
|
|
@@ -40,7 +35,10 @@ def test_read_file_content(mock_file_system):
|
|
|
40
35
|
|
|
41
36
|
with patch("builtins.open", mock_open(read_data=test_file_content)) as mock_file:
|
|
42
37
|
content = classifier.read_file_content(test_file_path)
|
|
43
|
-
mock_file.assert_called_once_with(
|
|
38
|
+
mock_file.assert_called_once_with(
|
|
39
|
+
test_file_path, 'r', encoding='utf-8')
|
|
40
|
+
mock_file.assert_called_once_with(
|
|
41
|
+
test_file_path, 'r', encoding='utf-8')
|
|
44
42
|
assert content == test_file_content
|
|
45
43
|
|
|
46
44
|
|
|
@@ -74,16 +72,25 @@ def test_read_file_with_fallback(mock_file_system):
|
|
|
74
72
|
|
|
75
73
|
with patch("builtins.open", mock_open(read_data=utf8_content)) as mock_file:
|
|
76
74
|
content = classifier.read_file_with_fallback(test_file_path)
|
|
77
|
-
mock_file.assert_called_once_with(
|
|
75
|
+
mock_file.assert_called_once_with(
|
|
76
|
+
test_file_path, 'r', encoding='utf-8')
|
|
77
|
+
mock_file.assert_called_once_with(
|
|
78
|
+
test_file_path, 'r', encoding='utf-8')
|
|
78
79
|
assert content == utf8_content
|
|
79
80
|
|
|
80
81
|
with patch("builtins.open", mock_open(read_data=utf8_content)) as mock_file:
|
|
81
|
-
mock_file.side_effect = [UnicodeDecodeError(
|
|
82
|
+
mock_file.side_effect = [UnicodeDecodeError(
|
|
83
|
+
"mock", b"", 0, 1, "reason"), mock_open(read_data=latin1_content).return_value]
|
|
84
|
+
mock_file.side_effect = [UnicodeDecodeError(
|
|
85
|
+
"mock", b"", 0, 1, "reason"), mock_open(read_data=latin1_content).return_value]
|
|
82
86
|
content = classifier.read_file_with_fallback(test_file_path)
|
|
83
87
|
assert content == latin1_content
|
|
84
88
|
|
|
85
89
|
with patch("builtins.open", mock_open(read_data=utf8_content)) as mock_file:
|
|
86
|
-
mock_file.side_effect = [UnicodeDecodeError(
|
|
90
|
+
mock_file.side_effect = [UnicodeDecodeError(
|
|
91
|
+
"mock", b"", 0, 1, "reason"), UnicodeDecodeError("mock", b"", 0, 1, "reason")]
|
|
92
|
+
mock_file.side_effect = [UnicodeDecodeError(
|
|
93
|
+
"mock", b"", 0, 1, "reason"), UnicodeDecodeError("mock", b"", 0, 1, "reason")]
|
|
87
94
|
content = classifier.read_file_with_fallback(test_file_path)
|
|
88
95
|
assert content is None
|
|
89
96
|
|
|
@@ -94,14 +101,23 @@ def test_file_contains_tags(mock_file_system):
|
|
|
94
101
|
file_content = "tag1 tag2 tag3"
|
|
95
102
|
|
|
96
103
|
with patch.object(classifier, 'read_file_with_fallback', return_value=file_content):
|
|
97
|
-
result = classifier.file_contains_tags(
|
|
104
|
+
result = classifier.file_contains_tags(
|
|
105
|
+
test_file_path, ['tag1', 'tag2'])
|
|
106
|
+
result = classifier.file_contains_tags(
|
|
107
|
+
test_file_path, ['tag1', 'tag2'])
|
|
98
108
|
assert result is True
|
|
99
109
|
|
|
100
|
-
result = classifier.file_contains_tags(
|
|
110
|
+
result = classifier.file_contains_tags(
|
|
111
|
+
test_file_path, ['tag1', 'tag4'])
|
|
112
|
+
result = classifier.file_contains_tags(
|
|
113
|
+
test_file_path, ['tag1', 'tag4'])
|
|
101
114
|
assert result is False
|
|
102
115
|
|
|
103
116
|
with patch.object(classifier, 'read_file_with_fallback', return_value=None):
|
|
104
|
-
result = classifier.file_contains_tags(
|
|
117
|
+
result = classifier.file_contains_tags(
|
|
118
|
+
test_file_path, ['tag1', 'tag2'])
|
|
119
|
+
result = classifier.file_contains_tags(
|
|
120
|
+
test_file_path, ['tag1', 'tag2'])
|
|
105
121
|
assert result is False
|
|
106
122
|
|
|
107
123
|
|
|
@@ -110,7 +126,7 @@ def test_classify_file(mock_file_system, mock_classifier):
|
|
|
110
126
|
test_file_path = "test_file.py"
|
|
111
127
|
|
|
112
128
|
with patch.object(classifier, 'is_binary_file', return_value=False), \
|
|
113
|
-
|
|
129
|
+
patch.object(classifier, 'file_contains_tags', return_value=True):
|
|
114
130
|
result = classifier.classify_file(test_file_path, tags=['tag1'])
|
|
115
131
|
assert result == 'py'
|
|
116
132
|
|
|
@@ -124,7 +140,10 @@ def test_classify_file(mock_file_system, mock_classifier):
|
|
|
124
140
|
|
|
125
141
|
|
|
126
142
|
def test_classify_files_skips_binary_files(mock_file_system, mock_classifier):
|
|
127
|
-
mock_file_system.walk.return_value = [
|
|
143
|
+
mock_file_system.walk.return_value = [
|
|
144
|
+
('.', [], ['file1.py', 'file2.txt', 'file3.bin'])]
|
|
145
|
+
mock_file_system.walk.return_value = [
|
|
146
|
+
('.', [], ['file1.py', 'file2.txt', 'file3.bin'])]
|
|
128
147
|
mock_file_system.path.join.side_effect = lambda root, file: f"{root}/{file}"
|
|
129
148
|
|
|
130
149
|
classifier = FileClassifier(mock_file_system)
|
|
@@ -145,7 +164,7 @@ def test_classify_file_no_match(mock_file_system, mock_classifier):
|
|
|
145
164
|
test_file_path = "test_file.unknown"
|
|
146
165
|
|
|
147
166
|
with patch.object(classifier, 'is_binary_file', return_value=False), \
|
|
148
|
-
|
|
167
|
+
patch.object(classifier, 'file_contains_tags', return_value=True):
|
|
149
168
|
result = classifier.classify_file(test_file_path, tags=['tag1'])
|
|
150
169
|
assert result is None
|
|
151
170
|
|
|
@@ -154,7 +173,7 @@ def test_classify_file_no_match(mock_file_system, mock_classifier):
|
|
|
154
173
|
(
|
|
155
174
|
[('.', [], ['file1.py', 'file2.txt', 'file3.bin'])],
|
|
156
175
|
['py', 'txt', 'bin'],
|
|
157
|
-
{'py': ['./file1.py'], 'txt': ['./file2.txt'], 'bin': ['./file3.bin']}
|
|
176
|
+
{'py': [{'file_path': './file1.py', 'title': 'file1'}], 'txt': [{'file_path': './file2.txt', 'title': 'file2'}], 'bin': [{'file_path': './file3.bin', 'title': 'file3'}]}
|
|
158
177
|
),
|
|
159
178
|
(
|
|
160
179
|
[('.', [], [])],
|
|
@@ -164,12 +183,12 @@ def test_classify_file_no_match(mock_file_system, mock_classifier):
|
|
|
164
183
|
(
|
|
165
184
|
[('.', [], ['file1.py', 'file2.unknown'])],
|
|
166
185
|
['py', None],
|
|
167
|
-
{'py': ['./file1.py'], 'txt': [], 'bin': []}
|
|
186
|
+
{'py': [{'file_path': './file1.py', 'title': 'file1'}], 'txt': [], 'bin': []}
|
|
168
187
|
),
|
|
169
188
|
(
|
|
170
189
|
[('.', [], ['file1.py', 'file2.txt', 'file3.unknown', 'file4.bin'])],
|
|
171
190
|
['py', 'txt', None, 'bin'],
|
|
172
|
-
{'py': ['./file1.py'], 'txt': ['./file2.txt'], 'bin': ['./file4.bin']}
|
|
191
|
+
{'py': [{'file_path': './file1.py', 'title': 'file1'}], 'txt': [{'file_path': './file2.txt', 'title': 'file2'}], 'bin': [{'file_path': './file4.bin', 'title': 'file4'}]}
|
|
173
192
|
),
|
|
174
193
|
])
|
|
175
194
|
def test_classify_files(mock_file_system, mock_classifier, walk_return_value, classify_file_side_effect, expected_result):
|
|
@@ -179,7 +198,7 @@ def test_classify_files(mock_file_system, mock_classifier, walk_return_value, cl
|
|
|
179
198
|
classifier = FileClassifier(mock_file_system)
|
|
180
199
|
|
|
181
200
|
with patch.object(classifier, 'classify_file', side_effect=classify_file_side_effect):
|
|
182
|
-
result = classifier.
|
|
201
|
+
result = classifier.classify_files()
|
|
183
202
|
|
|
184
203
|
assert result == expected_result
|
|
185
204
|
|
|
@@ -187,25 +206,45 @@ def test_classify_files(mock_file_system, mock_classifier, walk_return_value, cl
|
|
|
187
206
|
@pytest.mark.parametrize("files_by_classifier, expected_output", [
|
|
188
207
|
(
|
|
189
208
|
{'py': [MagicMock(file_path='file1.py')], 'txt': [], 'bin': []},
|
|
190
|
-
"PY Title files:\n - file1.py\n\n"
|
|
209
|
+
"PY Title files:\n - ./file1.py\n\n"
|
|
191
210
|
),
|
|
192
211
|
(
|
|
193
|
-
{'
|
|
194
|
-
"TXT Title files:\n - file2.txt\n\n"
|
|
212
|
+
{'txt': [MagicMock(file_path='file2.txt')], 'py': [], 'bin': []},
|
|
213
|
+
"TXT Title files:\n - ./file2.txt\n\n"
|
|
195
214
|
),
|
|
196
215
|
(
|
|
197
|
-
{'
|
|
198
|
-
"BIN Title files:\n - file3.bin\n\n"
|
|
216
|
+
{'bin': [MagicMock(file_path='file3.bin')], 'py': [], 'txt': []},
|
|
217
|
+
"BIN Title files:\n - ./file3.bin\n\n"
|
|
199
218
|
),
|
|
200
219
|
(
|
|
201
|
-
{'py': [MagicMock(file_path='file1.py')], 'txt': [
|
|
202
|
-
|
|
220
|
+
{'py': [MagicMock(file_path='file1.py')], 'txt': [
|
|
221
|
+
MagicMock(file_path='file2.txt')], 'bin': []},
|
|
222
|
+
"PY Title files:\n - ./file1.py\n\nTXT Title files:\n - ./file2.txt\n\n"
|
|
203
223
|
),
|
|
204
224
|
])
|
|
205
225
|
def test_print_classified_files(mock_file_system, mock_classifier, mock_get_artefact_title, files_by_classifier, expected_output, capsys):
|
|
206
226
|
classifier = FileClassifier(mock_file_system)
|
|
207
|
-
|
|
208
227
|
classifier.print_classified_files(files_by_classifier)
|
|
209
|
-
|
|
210
228
|
captured = capsys.readouterr()
|
|
211
229
|
assert captured.out == expected_output
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_find_closest_artefact_name_match(mock_file_system):
|
|
233
|
+
classifier = FileClassifier(mock_file_system)
|
|
234
|
+
classifier.classify_files = MagicMock(return_value={
|
|
235
|
+
'py': [{'title': 'file1'}, {'title': 'file2'}],
|
|
236
|
+
'txt': []
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
# Exact match
|
|
240
|
+
assert classifier.find_closest_artefact_name_match(
|
|
241
|
+
'file1', 'py') == 'file1'
|
|
242
|
+
|
|
243
|
+
# Fuzzy match
|
|
244
|
+
with patch('ara_cli.file_classifier.find_closest_name_match', return_value='file2') as mock_fuzzy:
|
|
245
|
+
assert classifier.find_closest_artefact_name_match(
|
|
246
|
+
'file3', 'py') == 'file2'
|
|
247
|
+
mock_fuzzy.assert_called_once_with('file3', ['file1', 'file2'])
|
|
248
|
+
|
|
249
|
+
# No match for classifier
|
|
250
|
+
assert classifier.find_closest_artefact_name_match('file1', 'txt') is None
|
|
@@ -1,64 +1,45 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from unittest.mock import MagicMock, patch
|
|
3
|
-
from ara_cli.tag_extractor import TagExtractor
|
|
4
|
-
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
from ara_cli.tag_extractor import TagExtractor # Adjust the import based on your project structure
|
|
5
4
|
|
|
6
5
|
@pytest.fixture
|
|
7
|
-
def
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def mock_open_file(file, mode):
|
|
49
|
-
if mode == 'r':
|
|
50
|
-
return mock_open(read_data=file_content_map[file]).return_value
|
|
51
|
-
else:
|
|
52
|
-
raise ValueError("Unsupported mode")
|
|
53
|
-
|
|
54
|
-
with patch('builtins.open', mock_open_file):
|
|
55
|
-
tag_extractor = TagExtractor(file_system=mock_file_system)
|
|
56
|
-
|
|
57
|
-
# Test without navigating to target
|
|
58
|
-
tags = tag_extractor.extract_tags(navigate_to_target=False)
|
|
59
|
-
assert tags == ['status1', 'tag1', 'user_user1']
|
|
60
|
-
|
|
61
|
-
# Test with navigating to target
|
|
62
|
-
tags = tag_extractor.extract_tags(navigate_to_target=True)
|
|
63
|
-
mock_directory_navigator.return_value.navigate_to_target.assert_called_once()
|
|
64
|
-
assert tags == ['status1', 'tag1', 'user_user1']
|
|
6
|
+
def artefact():
|
|
7
|
+
"""Fixture to create a mock artefact object."""
|
|
8
|
+
class Artefact:
|
|
9
|
+
def __init__(self, tags, status, users):
|
|
10
|
+
self.tags = tags
|
|
11
|
+
self.status = status
|
|
12
|
+
self.users = users
|
|
13
|
+
|
|
14
|
+
return Artefact
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("navigate_to_target, artefact_data, expected_tags", [
|
|
17
|
+
(False, {'artefacts': [(
|
|
18
|
+
['tag1', 'tag2'], 'status1', ['user1', 'user2']
|
|
19
|
+
)]}, ['status1', 'tag1', 'tag2', 'user_user1', 'user_user2']),
|
|
20
|
+
(True, {'artefacts': [(
|
|
21
|
+
['tag3'], 'status2', ['user3']
|
|
22
|
+
)]}, ['status2', 'tag3', 'user_user3'])
|
|
23
|
+
])
|
|
24
|
+
@patch('ara_cli.template_manager.DirectoryNavigator')
|
|
25
|
+
@patch('ara_cli.artefact_reader.ArtefactReader')
|
|
26
|
+
def test_extract_tags(mock_artefact_reader, mock_directory_navigator, artefact, navigate_to_target, artefact_data, expected_tags):
|
|
27
|
+
# Mock the artefact reader to return artefact data
|
|
28
|
+
mock_artefact_reader.read_artefacts.return_value = {'key': [artefact(*data) for data in artefact_data['artefacts']]}
|
|
29
|
+
|
|
30
|
+
# Mock the directory navigator
|
|
31
|
+
mock_navigator_instance = mock_directory_navigator.return_value
|
|
32
|
+
mock_navigator_instance.navigate_to_target = MagicMock()
|
|
33
|
+
|
|
34
|
+
tag_extractor = TagExtractor()
|
|
35
|
+
|
|
36
|
+
# Run the extract_tags method
|
|
37
|
+
result = tag_extractor.extract_tags(navigate_to_target=navigate_to_target)
|
|
38
|
+
|
|
39
|
+
# Assertions
|
|
40
|
+
if navigate_to_target:
|
|
41
|
+
mock_navigator_instance.navigate_to_target.assert_called_once()
|
|
42
|
+
else:
|
|
43
|
+
mock_navigator_instance.navigate_to_target.assert_not_called()
|
|
44
|
+
|
|
45
|
+
assert result == expected_tags
|
ara_cli/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1.9.
|
|
2
|
+
__version__ = "0.1.9.52" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
|