| #!/usr/bin/env vpython3 |
| # Copyright 2025 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Tests for lint_promptfoo_testcases.""" |
| |
| import os |
| import unittest |
| from unittest import mock |
| |
| import lint_promptfoo_testcases |
| |
| |
| class LintPromptfooTestcasesTest(unittest.TestCase): |
| |
| def setUp(self): |
| self.mock_file_path = 'agents/test.promptfoo.yaml' |
| |
| @mock.patch('lint_promptfoo_testcases._get_chromium_src_path', |
| return_value='/mock/chromium/src') |
| @mock.patch('os.path.exists', return_value=True) |
| def test_valid_file_references(self, _mock_exists, _mock_get_src_path): |
| """Tests that no errors are returned for a valid config.""" |
| data = { |
| 'providers': [{ |
| 'config': { |
| 'changes': [{ |
| 'apply': 'file://my_patch.diff' |
| }, { |
| 'apply': 'file://my_patch_1.diff' |
| }], |
| 'templates': ['file://my_template.txt'], |
| 'extensions': ['my_extension'] |
| } |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 0) |
| |
| @mock.patch('lint_promptfoo_testcases._get_chromium_src_path', |
| return_value='/mock/chromium/src') |
| @mock.patch('os.path.exists', return_value=False) |
| def test_non_existent_file(self, mock_exists, _mock_get_src_path): |
| """Tests that an error is returned for a non-existent file.""" |
| data = { |
| 'providers': [{ |
| 'config': { |
| 'changes': [{ |
| 'apply': 'file://agents/my_patch.diff' |
| }], |
| } |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn('refers to a non-existent file', errors[0]) |
| # Check that os.path.exists was called with the absolute path. |
| expected_path = os.path.join('/mock/chromium/src', 'agents', |
| 'my_patch.diff') |
| mock_exists.assert_called_with(expected_path) |
| |
| @mock.patch('lint_promptfoo_testcases._get_chromium_src_path', |
| return_value='/mock/chromium/src') |
| @mock.patch('os.path.exists', return_value=False) |
| def test_non_existent_extension(self, mock_exists, _mock_get_src_path): |
| """Tests that an error is returned for a non-existent extension.""" |
| data = { |
| 'providers': [{ |
| 'config': { |
| 'extensions': ['non_existent_extension'] |
| } |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn('refers to a non-existent extension', errors[0]) |
| expected_path = os.path.join('/mock/chromium/src', 'agents', |
| 'extensions', 'non_existent_extension') |
| mock_exists.assert_called_with(expected_path) |
| |
| def test_extension_with_file_path(self): |
| """Tests that an error is returned for an extension with a file path.""" |
| data = { |
| 'providers': [{ |
| 'config': { |
| 'extensions': ['file://some/extension.py'] |
| } |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn( |
| 'contains a file path for an extension. Please use ' |
| 'the extension name instead', errors[0]) |
| |
| def test_non_string_path(self): |
| """Tests that a non-string path value returns an error.""" |
| data = { |
| 'providers': [{ |
| 'config': { |
| 'changes': [{ |
| 'apply': 12345 |
| }], |
| } |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn('contains a non-string file reference', errors[0]) |
| |
| def test_non_string_extension(self): |
| """Tests that a non-string extension value returns an error.""" |
| data = {'providers': [{'config': {'extensions': [12345]}}]} |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn('contains a non-string extension reference', errors[0]) |
| |
| def test_malformed_structure(self): |
| """Tests that the linter doesn't crash on malformed data.""" |
| data = { |
| 'providers': [{ |
| 'id': 'test', |
| 'config': 'not a dict' |
| }], |
| } |
| # Should run without raising exceptions and return an error. |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 1) |
| self.assertIn('"providers" field must have a dict "config" field', |
| errors[0]) |
| |
| def test_provider_without_config(self): |
| """Tests that a provider without a config field is valid.""" |
| data = { |
| 'providers': [{ |
| 'id': 'test-provider-without-config', |
| }] |
| } |
| errors = lint_promptfoo_testcases.check_test_case( |
| data, self.mock_file_path) |
| self.assertEqual(len(errors), 0) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |