diff --git a/sarracenia/config/__init__.py b/sarracenia/config/__init__.py index 2cd78536d..a3f4e01f7 100644 --- a/sarracenia/config/__init__.py +++ b/sarracenia/config/__init__.py @@ -114,7 +114,7 @@ def __repr__(self) -> str: 'post_documentRoot': None, 'post_baseDir': None, 'post_baseUrl': None, - 'post_format': 'v03', + 'post_format': None, 'realpathPost': False, 'recursive' : True, 'runStateThreshold_reject': 80, diff --git a/sarracenia/config/publisher.py b/sarracenia/config/publisher.py index 9c232155f..f52dfb2de 100644 --- a/sarracenia/config/publisher.py +++ b/sarracenia/config/publisher.py @@ -63,13 +63,17 @@ def __init__(self, options ): if hasattr(options,'tlsRigour') : self['tlsRigour'] = options.tlsRigour - if hasattr(options,'post_format') : + if hasattr(options, 'post_format') and options.post_format is not None: self['format'] = options.post_format - elif hasattr(options,'post_topicPrefix') and options.post_topicPrefix[0] in [ 'v02', 'v03' ]: + elif hasattr(options, 'post_topicPrefix') and options.post_topicPrefix \ + and options.post_topicPrefix[0] in ['v02', 'v03']: self['format'] = options.post_topicPrefix[0] else: self['format'] = 'v03' + # exportAny and exportMine expect 'post_format' in the options dict + self['post_format'] = self['format'] + if hasattr(options,'post_topicPrefix') and options.post_topicPrefix: self['topicPrefix'] = options.post_topicPrefix elif hasattr(options, 'topicPrefix') and options.topicPrefix: diff --git a/sarracenia/moth/__init__.py b/sarracenia/moth/__init__.py index 37c1f8b09..e36907dfc 100644 --- a/sarracenia/moth/__init__.py +++ b/sarracenia/moth/__init__.py @@ -271,6 +271,8 @@ def pubFactory(props) -> 'Moth': props['broker'] = broker if 'exchange' in publisher: props['exchange'] = publisher['exchange'] + if 'format' in publisher: + props['format'] = publisher['format'] elif not props['broker']: logger.error('no broker specified') @@ -347,6 +349,8 @@ def __init__(self, props=None, is_subscriber=True) -> None: self.o['exchange'] = publisher['exchange'] if 'topicPrefix' in publisher: self.o['topicPrefix'] = publisher['topicPrefix'] + if 'format' in publisher: + self.o['format'] = publisher['format'] # apply settings from props. if 'settings' in self.o: diff --git a/tests/sarracenia/config/publisher_test.py b/tests/sarracenia/config/publisher_test.py new file mode 100644 index 000000000..6d7b47b66 --- /dev/null +++ b/tests/sarracenia/config/publisher_test.py @@ -0,0 +1,105 @@ +import pytest +from tests.conftest import * +from unittest.mock import MagicMock + +from sarracenia.config.publisher import Publisher + + +def make_options(**overrides): + """Minimal options object for Publisher.""" + opts = MagicMock() + opts.post_broker = MagicMock() + opts.post_broker.url = MagicMock() + opts.post_broker.url.username = 'tsource' + opts.post_broker.url.scheme = 'amqp' + opts.post_exchange = ['xs_tsource'] + opts.post_baseDir = '/tmp' + opts.post_baseUrl = 'http://localhost' + opts.post_exchangeSplit = 0 + + # defaults matching config/__init__.py + opts.post_format = None + opts.post_topicPrefix = ['v03', 'post'] + + for k, v in overrides.items(): + setattr(opts, k, v) + + return opts + + +def test_format_defaults_to_v03(): + """When neither post_format nor post_topicPrefix is set, default to v03.""" + opts = make_options() + del opts.post_topicPrefix + del opts.post_format + pub = Publisher(opts) + assert pub['format'] == 'v03' + assert pub['post_format'] == 'v03' + + +def test_format_derived_from_v02_topicprefix(): + """post_topicPrefix v02.post should derive format v02. + + This is the bug that Peter found -- post_format defaulted to 'v03' + which always took priority, so post_topicPrefix was never used. + """ + opts = make_options(post_topicPrefix=['v02', 'post']) + pub = Publisher(opts) + assert pub['format'] == 'v02' + assert pub['post_format'] == 'v02' + + +def test_format_derived_from_v03_topicprefix(): + """post_topicPrefix v03.post should derive format v03.""" + opts = make_options(post_topicPrefix=['v03', 'post']) + pub = Publisher(opts) + assert pub['format'] == 'v03' + assert pub['post_format'] == 'v03' + + +def test_explicit_post_format_overrides_topicprefix(): + """If user explicitly sets post_format, it wins over topicPrefix.""" + opts = make_options( + post_format='v03', + post_topicPrefix=['v02', 'post'], + ) + pub = Publisher(opts) + assert pub['format'] == 'v03' + assert pub['post_format'] == 'v03' + + +def test_explicit_v02_post_format(): + """If user explicitly sets post_format v02, use it.""" + opts = make_options(post_format='v02') + pub = Publisher(opts) + assert pub['format'] == 'v02' + assert pub['post_format'] == 'v02' + + +def test_topicprefix_fallback_is_empty_list(): + """When neither post_topicPrefix nor topicPrefix is set, fallback must + be [] (empty list), not None. Peter fixed this in bab4b9424 -- None + causes TypeError when downstream code iterates or concatenates.""" + opts = make_options() + del opts.post_topicPrefix + del opts.topicPrefix + pub = Publisher(opts) + assert pub['topicPrefix'] == [], \ + "topicPrefix fallback must be [] not None (see commit bab4b9424)" + + +def test_basedir_missing_no_keyerror(): + """Publisher must not raise KeyError when baseDir is absent from the + dict. The guard must use 'or' (short-circuit) not 'and'.""" + opts = make_options(post_baseUrl='file:/data/incoming') + del opts.post_baseDir + # This must not raise KeyError + pub = Publisher(opts) + assert pub['baseDir'] == '/data/incoming' + + +def test_basedir_empty_string_derives_from_url(): + """When baseDir is set but empty, it should still derive from baseUrl.""" + opts = make_options(post_baseDir='', post_baseUrl='file:/data/output') + pub = Publisher(opts) + assert pub['baseDir'] == '/data/output'