test_api.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # -*- coding:utf-8 -*-
  2. from unittest.mock import MagicMock, patch
  3. import pytest
  4. from dw_base.ds.api import DSClient
  5. @pytest.fixture
  6. def client(tmp_path):
  7. conf = tmp_path / 'ds.ini'
  8. conf.write_text(
  9. '[dolphinscheduler]\n'
  10. 'base_url = http://example/dolphinscheduler\n'
  11. 'token = TEST_TOKEN\n',
  12. encoding='utf-8',
  13. )
  14. return DSClient(conf_path=str(conf))
  15. def test_init_loads_conf(client):
  16. assert client.base_url == 'http://example/dolphinscheduler'
  17. assert client.token == 'TEST_TOKEN'
  18. assert client.session.headers['token'] == 'TEST_TOKEN'
  19. def test_init_missing_conf_raises():
  20. with pytest.raises(RuntimeError, match='DS 配置文件不存在'):
  21. DSClient(conf_path='/nonexistent/path.ini')
  22. def test_init_strips_trailing_slash(tmp_path):
  23. conf = tmp_path / 'ds.ini'
  24. conf.write_text(
  25. '[dolphinscheduler]\n'
  26. 'base_url = http://example/dolphinscheduler/\n'
  27. 'token = T\n',
  28. encoding='utf-8',
  29. )
  30. c = DSClient(conf_path=str(conf))
  31. assert c.base_url == 'http://example/dolphinscheduler'
  32. def test_get_2xx_returns_json(client):
  33. with patch.object(client.session, 'get') as mock_get:
  34. resp = MagicMock(status_code=200)
  35. resp.json.return_value = {'code': 0, 'data': 'ok'}
  36. mock_get.return_value = resp
  37. assert client.get('/projects') == {'code': 0, 'data': 'ok'}
  38. mock_get.assert_called_once_with(
  39. 'http://example/dolphinscheduler/projects', params=None)
  40. def test_get_strips_leading_slash(client):
  41. """path 带或不带前导 / 都拼成相同 URL。"""
  42. with patch.object(client.session, 'get') as mock_get:
  43. resp = MagicMock(status_code=200)
  44. resp.json.return_value = {}
  45. mock_get.return_value = resp
  46. client.get('projects')
  47. assert mock_get.call_args[0][0] == 'http://example/dolphinscheduler/projects'
  48. def test_get_non_2xx_raises(client):
  49. with patch.object(client.session, 'get') as mock_get:
  50. mock_get.return_value = MagicMock(status_code=500, text='Internal Server Error')
  51. with pytest.raises(RuntimeError, match='GET .*failed rc=500'):
  52. client.get('/projects')
  53. def test_get_2xx_non_json_raises_friendly(client):
  54. """新特性:path 错被 SPA fallback 时抛 RuntimeError 含 raw text 前 200 字符。"""
  55. with patch.object(client.session, 'get') as mock_get:
  56. resp = MagicMock(status_code=200)
  57. resp.json.side_effect = ValueError('Expecting value')
  58. resp.text = '<!DOCTYPE html><html>fallback</html>'
  59. mock_get.return_value = resp
  60. with pytest.raises(RuntimeError) as exc:
  61. client.get('/wrong/path')
  62. msg = str(exc.value)
  63. assert '返回非 JSON' in msg
  64. assert 'SPA fallback' in msg
  65. assert '<!DOCTYPE html>' in msg
  66. def test_get_2xx_non_json_truncates_to_200(client):
  67. with patch.object(client.session, 'get') as mock_get:
  68. resp = MagicMock(status_code=200)
  69. resp.json.side_effect = ValueError()
  70. resp.text = 'X' * 500
  71. mock_get.return_value = resp
  72. with pytest.raises(RuntimeError) as exc:
  73. client.get('/p')
  74. # 错误信息含 200 个 X,不含 500 个
  75. assert 'X' * 200 in str(exc.value)
  76. assert 'X' * 201 not in str(exc.value)
  77. def test_post_2xx_returns_json(client):
  78. with patch.object(client.session, 'post') as mock_post:
  79. resp = MagicMock(status_code=200)
  80. resp.json.return_value = {'ok': True}
  81. mock_post.return_value = resp
  82. assert client.post('/foo', json_body={'k': 'v'}) == {'ok': True}
  83. mock_post.assert_called_once_with(
  84. 'http://example/dolphinscheduler/foo', json={'k': 'v'})
  85. def test_post_2xx_non_json_raises_friendly(client):
  86. with patch.object(client.session, 'post') as mock_post:
  87. resp = MagicMock(status_code=200)
  88. resp.json.side_effect = ValueError()
  89. resp.text = 'NOT JSON'
  90. mock_post.return_value = resp
  91. with pytest.raises(RuntimeError, match='POST.*返回非 JSON'):
  92. client.post('/foo')
  93. def test_post_non_2xx_raises(client):
  94. with patch.object(client.session, 'post') as mock_post:
  95. mock_post.return_value = MagicMock(status_code=403, text='Forbidden')
  96. with pytest.raises(RuntimeError, match='POST .*failed rc=403'):
  97. client.post('/foo')