)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"40b7321c5d91eb16f5a3aad21309767dfc2e81d8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"a36d7e74_8a84d3d9","updated":"2026-04-10 07:43:49.000000000","message":"I think Clay is right in regards to the get in the elif, but more of a NIT.\n\nTests seem good. some moving around and pulling out the Null case from others, which now that it\u0027s handled in its own case in the code makes sense to me.\n\nif this it someone we want in before the release then I\u0027m good with it. It solves the bug and tests seem more readable because of it. I\u0027ll +2 it so tomorrow if you decide fix the `.get` you can say based on my previous +2 and get it in before the downstream release.","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"4d903caa_96e170e6","updated":"2026-04-10 01:07:55.000000000","message":"sadly an incomplete review with scattered and uncollected thoughts 😭\n\nwould you believe the day got away from me AGAIN!?\n\nI\u0027m almost 100% this change in swift/ is exactly what we need to merge fix the bug tho - the question of \"merge ready\" is mostly about \"are /tests DONE - what else might we need vs want\" - I didn\u0027t finish the list, must less evaluate them sufficiently to render an honest gut-check; I\u0027m warm in general - just not done.  I\u0027m sorry - that\u0027s not the fault of the patch or the author - that\u0027s on me.  I\u0027ll try to come back to this again tomorrow.","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"441e7d0712e25705b9e3b8ae18f499dff31d3b6e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"6670458d_33964c29","updated":"2026-04-06 15:57:34.000000000","message":"seems like we both came up with ~ the same idea:\n\n982705: WIP: ?version_marker\u003dnull treat null as newest | https://review.opendev.org/c/openstack/swift/+/982705\n\n^ I don\u0027t know if that probe test is any good; your unit/func tests look better","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"bc6b15c8_5fb6ff91","updated":"2026-04-13 09:37:03.000000000","message":"Clay - thanks for review and +2 🙂","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"40f7f61e_393608b0","updated":"2026-04-10 22:09:00.000000000","message":"FWIW this change works fine with the probe test I had in the other abandoned change linked off the bug.\n\n984134: wip test: s3 version suspended null marker | https://review.opendev.org/c/openstack/swift/+/984134\n\n^ I\u0027m keeping it hanging around for now (in a new WIP) even tho I think the func-test(s) in THIS change say the same thing and are pretty much strictly better than a probetest that doesn\u0027t require any backend failures (???).\n\nThe s3api difference w/ version_marker\u003dnull when no null version exists is going to be interesting tho!  I think it will relate to the XXX is_latest bugfix.\n\nBug #2147042 “versioning: listing is_latest flag may be wrong wh...” : https://bugs.launchpad.net/swift/+bug/2147042\n\nStill seems like progress to fix pagination, I agree with the commit message Closes-Bug: #2146885\n\n... version_marker\u003dnull almost definitely shouldn\u0027t skip versions - and *certainly* not if a null version *exists*\n\nI think these new tests are sufficient, and the test changes/cleanup are a BIG improvement.  We can keep working on version listing test infra \u0026 bugs in follow-ups but I didn\u0027t see any gaps here that jumped out as strictly necessary/lacking (these test changes seem complete) nor anything here that makes it harder to improve stuff later - if anything I expect this effort should make going forward easier.  KUDOS!","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"}],"swift/common/middleware/versioned_writes/object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":true,"context_lines":[{"line_number":1120,"context_line":"            if \u0027version_marker\u0027 not in params:"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1122,"context_line":"                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all timestamps"},{"line_number":1123,"context_line":"            elif params.get(\u0027version_marker\u0027) \u003d\u003d \u0027null\u0027:"},{"line_number":1124,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1125,"context_line":"                    params[\u0027marker\u0027])  # just before all timestamps"},{"line_number":1126,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":2,"id":"059874f7_8013a7cf","line":1123,"updated":"2026-04-10 01:07:55.000000000","message":"is it helpful/necessary to use `params.get()` here or as an elif to `\u0027version_marker\u0027 not in params` would ti be approprite to say more simply:\n\n```\nelif params[\u0027version_marker\u0027] \u003d\u003d \u0027null\u0027\n```\n\nto highlight: *it\u0027s there* and if it\u0027s exactly `\u0027null\u0027` do this!\n\nI could sort of imagine someone quickly mis-reading `.get() \u003d\u003d \u0027null\u0027` as `.get() \u003d\u003d None`","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"40b7321c5d91eb16f5a3aad21309767dfc2e81d8","unresolved":true,"context_lines":[{"line_number":1120,"context_line":"            if \u0027version_marker\u0027 not in params:"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1122,"context_line":"                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all timestamps"},{"line_number":1123,"context_line":"            elif params.get(\u0027version_marker\u0027) \u003d\u003d \u0027null\u0027:"},{"line_number":1124,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1125,"context_line":"                    params[\u0027marker\u0027])  # just before all timestamps"},{"line_number":1126,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":2,"id":"971a3510_07c9ae23","line":1123,"in_reply_to":"059874f7_8013a7cf","updated":"2026-04-10 07:43:49.000000000","message":"Yup, that makes sense to me, we\u0027ve know that it IS there. So Clay\u0027s suggestion is more correct. Happy to NIT it, but if you change it then great!","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":false,"context_lines":[{"line_number":1120,"context_line":"            if \u0027version_marker\u0027 not in params:"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1122,"context_line":"                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all timestamps"},{"line_number":1123,"context_line":"            elif params.get(\u0027version_marker\u0027) \u003d\u003d \u0027null\u0027:"},{"line_number":1124,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1125,"context_line":"                    params[\u0027marker\u0027])  # just before all timestamps"},{"line_number":1126,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":2,"id":"d348178c_b874584d","line":1123,"in_reply_to":"971a3510_07c9ae23","updated":"2026-04-10 10:55:47.000000000","message":"Done","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"}],"test/functional/test_object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":true,"context_lines":[{"line_number":1683,"context_line":"            \u0027hash\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1684,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1685,"context_line":"            \u0027version_id\u0027: obj1_v1[\u0027id\u0027],"},{"line_number":1686,"context_line":"        }])"},{"line_number":1687,"context_line":""},{"line_number":1688,"context_line":"    def test_list_marker_and_version_marker(self):"},{"line_number":1689,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"}],"source_content_type":"text/x-python","patch_set":2,"id":"3ce62f6f_8205905e","side":"PARENT","line":1686,"updated":"2026-04-10 01:07:55.000000000","message":"not obvious at a glance why it\u0027s approprite to delete this test; I sort of get the idea that *maybe* `test_list_marker_and_version_marker_is_null` becomes partially redundant with `test_list_versions_pagination` - but the assertion:\n\n```\n        self.assertEqual(exp_obj_versions,\n                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])\n                          for obj in actual_list])\n```\n\n*seems* slightly weaker than all this `content_type` \u0026 `hash` assertion stuff; like this test didn\u0027t BREAK with this change (did it!?)","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":true,"context_lines":[{"line_number":1683,"context_line":"            \u0027hash\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1684,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1685,"context_line":"            \u0027version_id\u0027: obj1_v1[\u0027id\u0027],"},{"line_number":1686,"context_line":"        }])"},{"line_number":1687,"context_line":""},{"line_number":1688,"context_line":"    def test_list_marker_and_version_marker(self):"},{"line_number":1689,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"}],"source_content_type":"text/x-python","patch_set":2,"id":"6db3515d_69289e9b","side":"PARENT","line":1686,"in_reply_to":"3ce62f6f_8205905e","updated":"2026-04-10 10:55:47.000000000","message":"\u003e  like this test didn\u0027t BREAK with this change (did it!?)\n\nIt did break, it asserts exactly the buggy behaviour 😞\n\nIt seemed redundant to fix this and add the pagination test, but fair point there is some loss of coverage. I will reinstate and fix.","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":false,"context_lines":[{"line_number":1683,"context_line":"            \u0027hash\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1684,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1685,"context_line":"            \u0027version_id\u0027: obj1_v1[\u0027id\u0027],"},{"line_number":1686,"context_line":"        }])"},{"line_number":1687,"context_line":""},{"line_number":1688,"context_line":"    def test_list_marker_and_version_marker(self):"},{"line_number":1689,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"}],"source_content_type":"text/x-python","patch_set":2,"id":"30bab90d_86828dba","side":"PARENT","line":1686,"in_reply_to":"6db3515d_69289e9b","updated":"2026-04-10 22:09:00.000000000","message":"Acknowledged","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":true,"context_lines":[{"line_number":1269,"context_line":"            \u0027Content-Type\u0027: \u0027text/jibberish11\u0027,"},{"line_number":1270,"context_line":"            \u0027ETag\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1271,"context_line":"        }, return_resp\u003dTrue)"},{"line_number":1272,"context_line":"        obj1_v1[\u0027id\u0027] \u003d resp.getheader(\u0027x-object-version-id\u0027)"},{"line_number":1273,"context_line":""},{"line_number":1274,"context_line":"        # v2"},{"line_number":1275,"context_line":"        resp \u003d obj.write(b\u0027version2\u0027, hdrs\u003d{"}],"source_content_type":"text/x-python","patch_set":2,"id":"1057d6af_584aabe7","line":1272,"updated":"2026-04-10 01:07:55.000000000","message":"would it be helpful/justified for this setup-helper to use the new `self.put_version()` or is the thinking that this `_prep_object_versions` setup is too opionated and should be *avoided* in NEW tests in-favor of \"per-test customized setups\"\n\nLike in general I prefer tests that are more DAMP, but I also have some experience with \"Test Personas\" - where sometimes it\u0027s useful to have a common set of representative fixtures that cover all the interesting corner cases so that when you test some new behavior you remember to consider all the different vagaries of data it needs to consider and similarly if you add some new form of tom-foolery to your common test setup - you get a chance to update all the tests who are trying to assert specific behaviors in the face of an exhaustive set of all the complicated forms of data: e.g. null with no version after it, version with a null after it, null with a NULL after it, etc\n\nobviously the choice of \"common setup across a set of tests\" vs \"targeted specific tests with completely independent setup\" isn\u0027t something you get to make a *general* decision about w/o a whole bunch of nuanced \"it depends\"\n\n... I\u0027m mostly just trying to figure out the extent this diff is trying to make an opionated judgement claim about what\u0027s approprite for this `TestContainerOperations` class - or maybe the `test.func.test_object_versioning` *module* - b/c we\u0027ve gone from N test_cases that consume `_prep_object_versions` to N-1 and Y test_cases in the class that do NOT consume `_prep_object_versions` to Y+2 - which feels like a shift AWAY from a almost-but-not-quite consistent consumption of `_prep_object_versions` - which seems to be a pattern across two TestCases in the module \n\nhere\u0027s what cluade thinks about the \"pattern\"\n\n```\n● Here\u0027s the breakdown. Two classes define _prep_object_versions:                                                                                                                                                                                       \n                                                                                          \n  ---                                                                                                                                                                                                                                                   \n  TestContainerOperations (line 1258), defines helper at line 1260                                                                                                                                                                                      \n                                                                                                                                                                                                                                                        \n  12 test methods USE _prep_object_versions:                                                                                                                                                                                                            \n  - test_list_all_versions (1357) — calls at 1359                                                                                                                                                                                                       \n  - test_list_all_versions_reverse (1411) — calls at 1413\n  - test_list_versions_prefix (1465) — calls at 1468                                                                                                                                                                                                    \n  - test_list_versions_prefix_reverse (1507) — calls at 1510                                                                                                                                                                                            \n  - test_list_limit (1549) — calls at 1551                                                                                                                                                                                                              \n  - test_list_limit_marker (1584) — calls at 1586                                                                                                                                                                                                       \n  - test_list_marker (1613) — calls at 1615                                                                                                                                                                                                             \n  - test_list_marker_and_version_marker (1654) — calls at 1656                                                                                                                                                                                          \n  - test_list_marker_and_version_marker_reverse (1812) — calls at 1814                                                                                                                                                                                  \n  - test_list_prefix_version_marker (1849) — calls at 1851                                                                                                                                                                                              \n  - test_list_prefix_version_marker_reverse (1879) — calls at 1881\n  - test_bytes_count (2166) — calls at 2188                                                                                                                                                                                                             \n                                                                                                                                                                                                                                                        \n  6 test methods do NOT use it:\n  - test_list_versions_pagination (1683)                                                                                                                                                                                                                \n  - test_list_versions_pagination_with_null_version (1728)                                                                                                                                                                                              \n  - test_unacceptable (1904)                              \n  - test_container_quota_bytes (2194)                                                                                                                                                                                                                   \n  - test_list_unversioned_container (2222)\n  - test_is_latest (2283)\n                                                                                                                                                                                                                                                        \n  ---\n  TestVersionsLocationWithVersioning (line 2757), defines helper at line 2777                                                                                                                                                                           \n                                                                             \n  2 test methods USE _prep_object_versions:\n  - test_list_with_versions_param (2803) — calls at 2804                                                                                                                                                                                                \n  - test_delete_with_null_version_id (2844) — calls at 2845\n                                                                                                                                                                                                                                                        \n  0 test methods do NOT use it.\n                                                                                                                                                                                                                                                        \n```\n\nAre we hoping as maintainers that breakdown should change over time?  Is shifting the (lack of?) \"consistency\" for reusing the `_prep_object_versions` setup one direction or the other \"better\"?","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":true,"context_lines":[{"line_number":1269,"context_line":"            \u0027Content-Type\u0027: \u0027text/jibberish11\u0027,"},{"line_number":1270,"context_line":"            \u0027ETag\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1271,"context_line":"        }, return_resp\u003dTrue)"},{"line_number":1272,"context_line":"        obj1_v1[\u0027id\u0027] \u003d resp.getheader(\u0027x-object-version-id\u0027)"},{"line_number":1273,"context_line":""},{"line_number":1274,"context_line":"        # v2"},{"line_number":1275,"context_line":"        resp \u003d obj.write(b\u0027version2\u0027, hdrs\u003d{"}],"source_content_type":"text/x-python","patch_set":2,"id":"5300744d_8d56246c","line":1272,"in_reply_to":"1057d6af_584aabe7","updated":"2026-04-10 10:55:47.000000000","message":"\u003ewould it be helpful/justified for this setup-helper to use the new self.put_version() \n\nYes, I could see it being applied, along with all the other places that tests repeat the sequence \u0027calculate etag, PUT obj, parse version id\u0027. But I\u0027m inclined to call it beyond the scope of this patch. I\u0027d end up with a commit message that has\n``Drive-By: ...``\nthat I live to regret ;-)\n\nMaybe if the patch was less urgent I\u0027d expand scope, but on this occasion I\u0027ll leave that for a follow-up.\n\n\u003e sometimes it\u0027s useful to have a common set of representative fixtures that cover all the interesting corner cases\n\n``_prep_object_versions`` doesn\u0027t cover the corner case that this patch addresses (a \u0027null\u0027 version), and again I think it is out of scope to make it so and deal with the fall out in other tests (every time you add an object in that helper you have to update *all* the call sites).\n\nFWIW I also dislike ``_prep_object_versions`` for its indexing of object names in reverse to their alphabetical ordering which I found counter-intuitive and a brain-tax.\n\nIn general, I think that *mandating* all tests use a common set of input conditions, that gets expanded over time, is very brittle.\n\nIn general, I think that having targeted helpers (primitives) that aid writing concise tests is useful because it encourage writing more tests. But often we only learn what those useful helpers are with hindsight, so they creep in over time (like ``put_version`` is here). Given the motivation and resource, we can do refactors to retrofit the helpers (like we have been doing with timestamps).\n\nAnd I completely agree that there is very little that we...\n\n\u003e get to make a general decision about w/o a whole bunch of nuanced \"it depends\"\n\n:) \n\n\u003e the extent this diff is trying to make an opionated judgement claim about what\u0027s approprite for this TestContainerOperations class\n\nZero, other than that listing tests seem to belong in ``TestContainerOperations``\n\n\u003e Are we hoping as maintainers that breakdown should change over time? \n\nI\u0027m somewhat ambivalent. We end up with different test patterns and helpers in many places as tests are incrementally added. I don\u0027t expect us to successfully enforce that every test ever added to this class will call ``_prep_object_versions`` (unless it\u0027s called in the ``setUp()``, or even ``setUpClass()``). \n\nI didn\u0027t find _prep_object_versions useful for this patch but that doesn\u0027t make me want to replace it.","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":false,"context_lines":[{"line_number":1269,"context_line":"            \u0027Content-Type\u0027: \u0027text/jibberish11\u0027,"},{"line_number":1270,"context_line":"            \u0027ETag\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1271,"context_line":"        }, return_resp\u003dTrue)"},{"line_number":1272,"context_line":"        obj1_v1[\u0027id\u0027] \u003d resp.getheader(\u0027x-object-version-id\u0027)"},{"line_number":1273,"context_line":""},{"line_number":1274,"context_line":"        # v2"},{"line_number":1275,"context_line":"        resp \u003d obj.write(b\u0027version2\u0027, hdrs\u003d{"}],"source_content_type":"text/x-python","patch_set":2,"id":"4b58525d_b94b76bd","line":1272,"in_reply_to":"5300744d_8d56246c","updated":"2026-04-10 22:09:00.000000000","message":"\u003e Drive-By: ... that I live to regret\n\nfor real 😂\n\n\u003e every time you add an object in that helper you have to update all the call sites\n\nthis IS the idea behind a common fixture list, \"if it\u0027s good for the goose\" and all that - if you find `test_list_versions_pagination_with_null_version` wants to have some of the test fixture data include a null version objects it\u0027s likely justifiable that we should also consider if `test_list_versions_prefix_reverse` does \"the right thing\" when *it\u0027s* stub data includes a null version.  i.e. the fact that you are fixing a bug and there\u0027s no existing fixture data suggests a GAP in the fixture data.  Functests are slightly different from unittests in this regard; you could even go as far as using a TestCase.setup to create all the fixtures you could possibly ever need and the blast with every querey param combination under the sun asserting you get the right conditions for all the versioned personas!\n\n\u003e FWIW I also dislike `_prep_object_versions` for its indexing of object names in reverse to their alphabetical ordering which I found counter-intuitive and a brain-tax\n\nThis is probably the real reason it\u0027s worth these new tests abandoning the \"standard\" test fixtures - the existing stuff just sucks; and who has time to make crapy test-infra better.\n\n\u003e I\u0027ll leave that for a follow-up.\n\nmakes sense to me!","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":true,"context_lines":[{"line_number":1681,"context_line":"        }])"},{"line_number":1682,"context_line":""},{"line_number":1683,"context_line":"    def test_list_versions_pagination(self):"},{"line_number":1684,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"},{"line_number":1685,"context_line":"        obj1_v1 \u003d self.put_version(obj1, \u0027v1\u0027)"},{"line_number":1686,"context_line":"        obj2 \u003d self.env.container.file(\u0027b\u0027 + Utils.create_name())"},{"line_number":1687,"context_line":"        obj2_v1 \u003d self.put_version(obj2, \u0027v1\u0027)"}],"source_content_type":"text/x-python","patch_set":2,"id":"de8ce3e2_ac7f4f67","line":1684,"updated":"2026-04-10 10:55:47.000000000","message":"I\u0027m going to rename these obj1, obj2, obj3 vars to obja, objb, objc to avoid any confusion with the vars in ``_prep_object_versions``","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":false,"context_lines":[{"line_number":1681,"context_line":"        }])"},{"line_number":1682,"context_line":""},{"line_number":1683,"context_line":"    def test_list_versions_pagination(self):"},{"line_number":1684,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"},{"line_number":1685,"context_line":"        obj1_v1 \u003d self.put_version(obj1, \u0027v1\u0027)"},{"line_number":1686,"context_line":"        obj2 \u003d self.env.container.file(\u0027b\u0027 + Utils.create_name())"},{"line_number":1687,"context_line":"        obj2_v1 \u003d self.put_version(obj2, \u0027v1\u0027)"}],"source_content_type":"text/x-python","patch_set":2,"id":"05e4e79c_4f4d8b70","line":1684,"in_reply_to":"de8ce3e2_ac7f4f67","updated":"2026-04-10 22:09:00.000000000","message":"Acknowledged","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":1681,"context_line":"        }])"},{"line_number":1682,"context_line":""},{"line_number":1683,"context_line":"    def test_list_versions_pagination(self):"},{"line_number":1684,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"},{"line_number":1685,"context_line":"        obj1_v1 \u003d self.put_version(obj1, \u0027v1\u0027)"},{"line_number":1686,"context_line":"        obj2 \u003d self.env.container.file(\u0027b\u0027 + Utils.create_name())"},{"line_number":1687,"context_line":"        obj2_v1 \u003d self.put_version(obj2, \u0027v1\u0027)"}],"source_content_type":"text/x-python","patch_set":2,"id":"b10718fb_c56bf42f","line":1684,"in_reply_to":"de8ce3e2_ac7f4f67","updated":"2026-04-13 09:37:03.000000000","message":"Done","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":true,"context_lines":[{"line_number":1712,"context_line":"        self.assertEqual(exp_obj_versions[:3],"},{"line_number":1713,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1714,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1715,"context_line":"        actual_full_list \u003d actual_sub_list"},{"line_number":1716,"context_line":""},{"line_number":1717,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"},{"line_number":1718,"context_line":"                         \u0027limit\u0027: 3,"}],"source_content_type":"text/x-python","patch_set":2,"id":"d7422857_6260e92e","line":1715,"updated":"2026-04-10 01:07:55.000000000","message":"is this an *actual_full*_list or more of a synthetic page-by-page list that we accumulate into?\n\nI feel like the *actual_full*_list is the original `actual_list` we got from the original limit-less request at L1702","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":false,"context_lines":[{"line_number":1712,"context_line":"        self.assertEqual(exp_obj_versions[:3],"},{"line_number":1713,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1714,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1715,"context_line":"        actual_full_list \u003d actual_sub_list"},{"line_number":1716,"context_line":""},{"line_number":1717,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"},{"line_number":1718,"context_line":"                         \u0027limit\u0027: 3,"}],"source_content_type":"text/x-python","patch_set":2,"id":"0891683a_0af8c413","line":1715,"in_reply_to":"d7422857_6260e92e","updated":"2026-04-10 10:55:47.000000000","message":"Done","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bf02ab9214c9c0e94200f8bc048a0ea2c5619829","unresolved":true,"context_lines":[{"line_number":1723,"context_line":"        self.assertEqual(exp_obj_versions[3:],"},{"line_number":1724,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1725,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1726,"context_line":"        actual_full_list.extend(actual_sub_list)"},{"line_number":1727,"context_line":""},{"line_number":1728,"context_line":"    def test_list_versions_pagination_with_null_version(self):"},{"line_number":1729,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"}],"source_content_type":"text/x-python","patch_set":2,"id":"95fbe862_f1a97b34","line":1726,"updated":"2026-04-10 01:07:55.000000000","message":"I feel like this is begging for a final assertion that the concat listings of the page-by-page results is *exactly* the same as `actual_list`","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1d806bec1b25b22620bcac76aadf817faa8677ce","unresolved":true,"context_lines":[{"line_number":1723,"context_line":"        self.assertEqual(exp_obj_versions[3:],"},{"line_number":1724,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1725,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1726,"context_line":"        actual_full_list.extend(actual_sub_list)"},{"line_number":1727,"context_line":""},{"line_number":1728,"context_line":"    def test_list_versions_pagination_with_null_version(self):"},{"line_number":1729,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"}],"source_content_type":"text/x-python","patch_set":2,"id":"e6ee04e8_c9e60cd8","line":1726,"in_reply_to":"95fbe862_f1a97b34","updated":"2026-04-10 10:55:47.000000000","message":"eek, yes I\u0027m sure that\u0027s what I intended! same as line 1808","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":false,"context_lines":[{"line_number":1723,"context_line":"        self.assertEqual(exp_obj_versions[3:],"},{"line_number":1724,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1725,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1726,"context_line":"        actual_full_list.extend(actual_sub_list)"},{"line_number":1727,"context_line":""},{"line_number":1728,"context_line":"    def test_list_versions_pagination_with_null_version(self):"},{"line_number":1729,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"}],"source_content_type":"text/x-python","patch_set":2,"id":"11c8dba4_3e829c3e","line":1726,"in_reply_to":"e6ee04e8_c9e60cd8","updated":"2026-04-10 22:09:00.000000000","message":"Acknowledged","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":1723,"context_line":"        self.assertEqual(exp_obj_versions[3:],"},{"line_number":1724,"context_line":"                         [(obj[\u0027name\u0027], obj[\u0027version_id\u0027], obj[\u0027is_latest\u0027])"},{"line_number":1725,"context_line":"                          for obj in actual_sub_list])"},{"line_number":1726,"context_line":"        actual_full_list.extend(actual_sub_list)"},{"line_number":1727,"context_line":""},{"line_number":1728,"context_line":"    def test_list_versions_pagination_with_null_version(self):"},{"line_number":1729,"context_line":"        obj1 \u003d self.env.container.file(\u0027a\u0027 + Utils.create_name())"}],"source_content_type":"text/x-python","patch_set":2,"id":"0873fb65_9a5fe156","line":1726,"in_reply_to":"e6ee04e8_c9e60cd8","updated":"2026-04-13 09:37:03.000000000","message":"Done","commit_id":"52b9de4a395a8dbd481213c64f19c29028bdce61"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":1570,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":1571,"context_line":"            \u0027content_type\u0027: \u0027text/jibberish20\u0027,"},{"line_number":1572,"context_line":"            \u0027hash\u0027: \u0027966634ebf2fc135707d6753692bf4b1e\u0027,"},{"line_number":1573,"context_line":"            \u0027is_latest\u0027: True,"},{"line_number":1574,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1575,"context_line":"        }, {"},{"line_number":1576,"context_line":"            \u0027name\u0027: obj1_v4[\u0027name\u0027],"}],"source_content_type":"text/x-python","patch_set":3,"id":"f55af462_2fae5634","line":1573,"updated":"2026-04-10 22:09:00.000000000","message":"\u003e XXX is_latest should be True for obj2_v1\n\nright!","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":1570,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":1571,"context_line":"            \u0027content_type\u0027: \u0027text/jibberish20\u0027,"},{"line_number":1572,"context_line":"            \u0027hash\u0027: \u0027966634ebf2fc135707d6753692bf4b1e\u0027,"},{"line_number":1573,"context_line":"            \u0027is_latest\u0027: True,"},{"line_number":1574,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1575,"context_line":"        }, {"},{"line_number":1576,"context_line":"            \u0027name\u0027: obj1_v4[\u0027name\u0027],"}],"source_content_type":"text/x-python","patch_set":3,"id":"4cef1a9c_70fbdfb8","line":1573,"in_reply_to":"f55af462_2fae5634","updated":"2026-04-13 09:37:03.000000000","message":"Acknowledged","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":1671,"context_line":"            \u0027content_type\u0027: \u0027text/jibberish20\u0027,"},{"line_number":1672,"context_line":"            \u0027hash\u0027: \u0027966634ebf2fc135707d6753692bf4b1e\u0027,"},{"line_number":1673,"context_line":"            # XXX is_latest should be True for obj2_v1"},{"line_number":1674,"context_line":"            # see https://bugs.launchpad.net/swift/+bug/2147042"},{"line_number":1675,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1676,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1677,"context_line":"        }, {"}],"source_content_type":"text/x-python","patch_set":3,"id":"6c1be519_341c029d","line":1674,"updated":"2026-04-10 22:09:00.000000000","message":"this alone could justify a `Related-Bug` in the commit message; but maybe it\u0027s better to just consider it on it\u0027s own - this should get cleaned up when that bug gets fixed.","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":1671,"context_line":"            \u0027content_type\u0027: \u0027text/jibberish20\u0027,"},{"line_number":1672,"context_line":"            \u0027hash\u0027: \u0027966634ebf2fc135707d6753692bf4b1e\u0027,"},{"line_number":1673,"context_line":"            # XXX is_latest should be True for obj2_v1"},{"line_number":1674,"context_line":"            # see https://bugs.launchpad.net/swift/+bug/2147042"},{"line_number":1675,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1676,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1677,"context_line":"        }, {"}],"source_content_type":"text/x-python","patch_set":3,"id":"fcade2bf_4fbae43b","line":1674,"in_reply_to":"6c1be519_341c029d","updated":"2026-04-13 09:37:03.000000000","message":"Acknowledged","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":1674,"context_line":"            # see https://bugs.launchpad.net/swift/+bug/2147042"},{"line_number":1675,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1676,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1677,"context_line":"        }, {"},{"line_number":1678,"context_line":"            \u0027name\u0027: obj1_v4[\u0027name\u0027],"},{"line_number":1679,"context_line":"            \u0027bytes\u0027: 0,"},{"line_number":1680,"context_line":"            \u0027content_type\u0027: \u0027application/x-deleted;swift_versions_deleted\u003d1\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"5569a552_8028d48e","line":1677,"updated":"2026-04-10 22:09:00.000000000","message":"this is super helpful!","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":1674,"context_line":"            # see https://bugs.launchpad.net/swift/+bug/2147042"},{"line_number":1675,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1676,"context_line":"            \u0027version_id\u0027: obj2_v1[\u0027id\u0027],"},{"line_number":1677,"context_line":"        }, {"},{"line_number":1678,"context_line":"            \u0027name\u0027: obj1_v4[\u0027name\u0027],"},{"line_number":1679,"context_line":"            \u0027bytes\u0027: 0,"},{"line_number":1680,"context_line":"            \u0027content_type\u0027: \u0027application/x-deleted;swift_versions_deleted\u003d1\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"073c6410_0d5f7b61","line":1677,"in_reply_to":"5569a552_8028d48e","updated":"2026-04-13 09:37:03.000000000","message":"Acknowledged","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"}],"test/unit/common/middleware/test_object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2760,"context_line":"    def test_list_versions_marker(self):"},{"line_number":2761,"context_line":"        listing_body \u003d [{"},{"line_number":2762,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2763,"context_line":"            \u0027name\u0027: \u0027non-versioned-obj\u0027,"},{"line_number":2764,"context_line":"            \u0027hash\u0027: \u0027etag\u0027,"},{"line_number":2765,"context_line":"            \u0027last_modified\u0027: \u00271970-01-01T00:00:05.000000\u0027,"},{"line_number":2766,"context_line":"            \u0027content_type\u0027: \u0027application/bar\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"415f2db5_800758e3","side":"PARENT","line":2763,"updated":"2026-04-10 22:09:00.000000000","message":"so much of this setup and assertions changed it\u0027s hard for me to see the behavior change.","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2803,"context_line":"            \u0027version_id\u0027: \u0027null\u0027,"},{"line_number":2804,"context_line":"            \u0027last_modified\u0027: \u00271970-01-01T00:00:05.000000\u0027,"},{"line_number":2805,"context_line":"            \u0027content_type\u0027: \u0027application/bar\u0027,"},{"line_number":2806,"context_line":"        }, {"},{"line_number":2807,"context_line":"            \u0027bytes\u0027: 9,"},{"line_number":2808,"context_line":"            \u0027name\u0027: \u0027obj\u0027,"},{"line_number":2809,"context_line":"            \u0027version_id\u0027: \u00270000000030.00000\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"7a6de3ee_f4ed853f","side":"PARENT","line":2806,"updated":"2026-04-10 22:09:00.000000000","message":"why even put this n \"expected\" if\n\na) it\u0027s not in any of the stub listings\nb) no one ever asserts it\u0027s there!\n\nnice cleanup.","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2836,"context_line":"            \u0027GET\u0027, \u0027/v1/a/c\u0027, swob.HTTPOk,"},{"line_number":2837,"context_line":"            {SYSMETA_VERSIONS_CONT: self.build_container_name(\u0027c\u0027),"},{"line_number":2838,"context_line":"             SYSMETA_VERSIONS_ENABLED: True},"},{"line_number":2839,"context_line":"            json.dumps(listing_body[1:]).encode(\u0027utf8\u0027))"},{"line_number":2840,"context_line":""},{"line_number":2841,"context_line":"        # marker"},{"line_number":2842,"context_line":"        req \u003d Request.blank("}],"source_content_type":"text/x-python","patch_set":3,"id":"259dca2c_bae334bb","side":"PARENT","line":2839,"updated":"2026-04-10 22:09:00.000000000","message":"I think this was a test bug?  given the asserted sub-request marker to the versions container was `\\x00obj\\x00` this response should have been empty?","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":2836,"context_line":"            \u0027GET\u0027, \u0027/v1/a/c\u0027, swob.HTTPOk,"},{"line_number":2837,"context_line":"            {SYSMETA_VERSIONS_CONT: self.build_container_name(\u0027c\u0027),"},{"line_number":2838,"context_line":"             SYSMETA_VERSIONS_ENABLED: True},"},{"line_number":2839,"context_line":"            json.dumps(listing_body[1:]).encode(\u0027utf8\u0027))"},{"line_number":2840,"context_line":""},{"line_number":2841,"context_line":"        # marker"},{"line_number":2842,"context_line":"        req \u003d Request.blank("}],"source_content_type":"text/x-python","patch_set":3,"id":"45f19d27_475ed1f4","side":"PARENT","line":2839,"in_reply_to":"259dca2c_bae334bb","updated":"2026-04-13 09:37:03.000000000","message":"yes this test was bogus, changing the marker at line 2843/2831 makes the test more reasonable","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2848,"context_line":"        self.assertIn((\u0027X-Versions-Enabled\u0027, \u0027True\u0027), headers)"},{"line_number":2849,"context_line":"        self.assertEqual(len(self.authorized), 1)"},{"line_number":2850,"context_line":"        self.assertRequestEqual(req, self.authorized[0])"},{"line_number":2851,"context_line":"        self.assertEqual(expected[1:], json.loads(body))"},{"line_number":2852,"context_line":"        self.assertEqual(["},{"line_number":2853,"context_line":"            (\u0027/v1/a/c\u0027,"},{"line_number":2854,"context_line":"             {\u0027format\u0027: \u0027json\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027versions\u0027: \u0027\u0027}),"}],"source_content_type":"text/x-python","patch_set":3,"id":"d6012ed7_a7ef770c","side":"PARENT","line":2851,"updated":"2026-04-10 22:09:00.000000000","message":"reading the existing test, from my understanding of the current behavior I\u0027m a little surprised that `marker\u003dobj` doesn\u0027t skip all versions of `obj`","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":2848,"context_line":"        self.assertIn((\u0027X-Versions-Enabled\u0027, \u0027True\u0027), headers)"},{"line_number":2849,"context_line":"        self.assertEqual(len(self.authorized), 1)"},{"line_number":2850,"context_line":"        self.assertRequestEqual(req, self.authorized[0])"},{"line_number":2851,"context_line":"        self.assertEqual(expected[1:], json.loads(body))"},{"line_number":2852,"context_line":"        self.assertEqual(["},{"line_number":2853,"context_line":"            (\u0027/v1/a/c\u0027,"},{"line_number":2854,"context_line":"             {\u0027format\u0027: \u0027json\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027versions\u0027: \u0027\u0027}),"}],"source_content_type":"text/x-python","patch_set":3,"id":"c8e2b05f_78eaedcd","side":"PARENT","line":2851,"in_reply_to":"d6012ed7_a7ef770c","updated":"2026-04-13 09:37:03.000000000","message":"Acknowledged","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2861,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2862,"context_line":""},{"line_number":2863,"context_line":"        # marker and version_marker\u003dnull"},{"line_number":2864,"context_line":"        self.app.clear_calls()"},{"line_number":2865,"context_line":"        req \u003d Request.blank("},{"line_number":2866,"context_line":"            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0026version_marker\u003dnull\u0027,"},{"line_number":2867,"context_line":"            environ\u003d{\u0027REQUEST_METHOD\u0027: \u0027GET\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"ce814103_28094e28","side":"PARENT","line":2864,"updated":"2026-04-10 22:09:00.000000000","message":"ok, so this diff is in no small part a test refactor/cleanup - I\u0027m not *against* the style of test that uses \"given this common set of objects in containers here\u0027s all the responses you get given various params\" like we see in func-tests ... but given the unittest FakeSwift.register pattern for container listings (where you have to set your stub-response before you assert which markers were used) can lead to the kind of test bugs fixed above where the stub response isn\u0027t what a container-server/real-proxy-app would ACTUALLY return to the mw for a given a set of listing params... I think smaller independent tests here is a big win.  KUDOS.","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2935,"context_line":"            json.dumps(versions_listing_body[2:]).encode(\u0027utf8\u0027))"},{"line_number":2936,"context_line":"        req \u003d Request.blank("},{"line_number":2937,"context_line":"            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0027"},{"line_number":2938,"context_line":"            \u0027\u0026version_marker\u003d0000000010.00000_2edcba9876123456\u0027,"},{"line_number":2939,"context_line":"            environ\u003d{\u0027REQUEST_METHOD\u0027: \u0027GET\u0027,"},{"line_number":2940,"context_line":"                     \u0027swift.cache\u0027: self.cache_version_on})"},{"line_number":2941,"context_line":"        status, headers, body \u003d self.call_ov(req)"}],"source_content_type":"text/x-python","patch_set":3,"id":"1b463f17_746ce997","side":"PARENT","line":2938,"updated":"2026-04-10 22:09:00.000000000","message":"oic, \"version w/ hexpart\" is an *existing* test (!!) - so maybe the hard coded timestamp and inversion strings was \"less good\"","commit_id":"694d25bb1a87f9426ac7ad1f3817c51f5bf13a34"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2850,"context_line":""},{"line_number":2851,"context_line":"    def test_list_versions_with_marker_and_version_marker_is_null(self):"},{"line_number":2852,"context_line":"        ts1, ts2, ts3 \u003d (self.ts() for _ in range(3))"},{"line_number":2853,"context_line":"        listing_body \u003d []"},{"line_number":2854,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2855,"context_line":"            \u0027bytes\u0027: 9,"},{"line_number":2856,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts3).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"75117f0d_a3ed8667","line":2853,"updated":"2026-04-10 22:09:00.000000000","message":"`test_list_versions_with_marker_and_version_marker_is_null` AND no null version exists","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":true,"context_lines":[{"line_number":2850,"context_line":""},{"line_number":2851,"context_line":"    def test_list_versions_with_marker_and_version_marker_is_null(self):"},{"line_number":2852,"context_line":"        ts1, ts2, ts3 \u003d (self.ts() for _ in range(3))"},{"line_number":2853,"context_line":"        listing_body \u003d []"},{"line_number":2854,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2855,"context_line":"            \u0027bytes\u0027: 9,"},{"line_number":2856,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts3).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"20f4c4fe_edabcc29","line":2853,"in_reply_to":"75117f0d_a3ed8667","updated":"2026-04-13 09:37:03.000000000","message":"\u003e `test_list_versions_with_marker_and_version_marker_is_null` AND no null version exists\n\nNo. The existence of a null version or not isn\u0027t relevant/exposed to the test because the backend listing marker param excludes it even if it did exist:\n\n* The null version would be a non-versioned \u0027obj\u0027 in the user container.\n* The user container marker is \u0027obj\u0027 therefore excludes \u0027obj\u0027 from the response from the user container.\n\n[off-topic] This \u0027blindness to null version\u0027 relates to the other bug re. the is_latest version being wrong...\n\nThe middleware cannot know that there is a non-versioned null version in the user-namespace if has excluded the user-namespace object from its backend listings!! Similarly, the middleware cannot know that a version is the one that is symlinked from user-namespace, and therefore the latest version, if it has excluded the user-namespace object from its backend listings!!\n\nI suspect that the fix for that is going to require adjusting the marker(s) so that the backend response includes *more* items than we want to return to the client, in order for the middleware to observe that there is a null version (or not), maybe drop it from the listing, and set is_latest accordingly on the first item in the client response.","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2930,"context_line":"            (\u0027/v1/a/%00versions%00c\u0027,"},{"line_number":2931,"context_line":"             {\u0027delimiter\u0027: \u0027\u0027,"},{"line_number":2932,"context_line":"              \u0027limit\u0027: \u0027\u0027,"},{"line_number":2933,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00\u0027,"},{"line_number":2934,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2935,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2936,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"}],"source_content_type":"text/x-python","patch_set":3,"id":"02e3ba8e_5bd3e4c5","line":2933,"updated":"2026-04-10 22:09:00.000000000","message":"right, so this is \"list all versions of obj\" - this does a good job of highlighting an existing test that was making the wrong assertions.\n\nalthough it\u0027s actually in a different test now - the diff makes it more clear it was updating an existing test (that also got refactored/extracted AFAICT)","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":false,"context_lines":[{"line_number":2930,"context_line":"            (\u0027/v1/a/%00versions%00c\u0027,"},{"line_number":2931,"context_line":"             {\u0027delimiter\u0027: \u0027\u0027,"},{"line_number":2932,"context_line":"              \u0027limit\u0027: \u0027\u0027,"},{"line_number":2933,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00\u0027,"},{"line_number":2934,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2935,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2936,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"}],"source_content_type":"text/x-python","patch_set":3,"id":"ae8535b2_9f5e4b60","line":2933,"in_reply_to":"02e3ba8e_5bd3e4c5","updated":"2026-04-13 09:37:03.000000000","message":"Acknowledged","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2937,"context_line":""},{"line_number":2938,"context_line":"    def test_list_versions_with_marker_and_version_marker(self):"},{"line_number":2939,"context_line":"        ts1, ts2 \u003d self.ts(), self.ts()"},{"line_number":2940,"context_line":"        listing_body \u003d []"},{"line_number":2941,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2942,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2943,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts1).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"106b1cab_baa729b8","line":2940,"updated":"2026-04-10 22:09:00.000000000","message":"and again \"and no null version exists\"","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":true,"context_lines":[{"line_number":2937,"context_line":""},{"line_number":2938,"context_line":"    def test_list_versions_with_marker_and_version_marker(self):"},{"line_number":2939,"context_line":"        ts1, ts2 \u003d self.ts(), self.ts()"},{"line_number":2940,"context_line":"        listing_body \u003d []"},{"line_number":2941,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2942,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2943,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts1).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"580a1b00_8206e6d3","line":2940,"in_reply_to":"106b1cab_baa729b8","updated":"2026-04-13 09:37:03.000000000","message":"same as line 2853: the test neither depends on nor assumes the existence of a null version or not.\n\nTBH, the more robust aspect of these tests is the assertions for what the backend requests params are.\n\nThe assertions on the response listing very much rely on the fake listing being correct.\n\n``listing_body \u003d []`` doesn\u0027t imply there is no null version of ``obj``, it just reflects that the marker excludes ``obj`` from the user container listing.","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2990,"context_line":""},{"line_number":2991,"context_line":"    def test_list_versions_with_marker_and_version_marker_with_hex_part(self):"},{"line_number":2992,"context_line":"        ts1, ts2 \u003d self.ts(), self.ts()"},{"line_number":2993,"context_line":"        # we do not expect this in reality but we want to test that a future"},{"line_number":2994,"context_line":"        # version_id with hex part is OK"},{"line_number":2995,"context_line":"        ts2 \u003d Timestamp(ts2, offset\u003d1)"},{"line_number":2996,"context_line":"        listing_body \u003d []"}],"source_content_type":"text/x-python","patch_set":3,"id":"6aa34d0a_2caac2af","line":2993,"updated":"2026-04-10 22:09:00.000000000","message":"you\u0027re just thinking 4th-dimensionally - that\u0027s not unrealistic! ;)","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"136616773cde3f5ab7093ee8a1a0de9c84543aa6","unresolved":true,"context_lines":[{"line_number":2993,"context_line":"        # we do not expect this in reality but we want to test that a future"},{"line_number":2994,"context_line":"        # version_id with hex part is OK"},{"line_number":2995,"context_line":"        ts2 \u003d Timestamp(ts2, offset\u003d1)"},{"line_number":2996,"context_line":"        listing_body \u003d []"},{"line_number":2997,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2998,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2999,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts1).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"5f4f450f_2e55c2cf","line":2996,"updated":"2026-04-10 22:09:00.000000000","message":"oic, a version with an *offset* isn\u0027t supa dupa realistic I guess.  But on master that\u0027s how you can get a hexpart.  I gotchu.\n\nI think ideally we\u0027d have just added this test to a different change?  Maybe the jitter patch?","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c843f6ca214b62042d070c0e5d4adbc333421156","unresolved":true,"context_lines":[{"line_number":2993,"context_line":"        # we do not expect this in reality but we want to test that a future"},{"line_number":2994,"context_line":"        # version_id with hex part is OK"},{"line_number":2995,"context_line":"        ts2 \u003d Timestamp(ts2, offset\u003d1)"},{"line_number":2996,"context_line":"        listing_body \u003d []"},{"line_number":2997,"context_line":"        versions_listing_body \u003d [{"},{"line_number":2998,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2999,"context_line":"            \u0027name\u0027: self.build_object_name(\u0027obj\u0027, (~ts1).internal),"}],"source_content_type":"text/x-python","patch_set":3,"id":"a996e084_428c9bb2","line":2996,"in_reply_to":"5f4f450f_2e55c2cf","updated":"2026-04-13 09:37:03.000000000","message":"too many things going on concurrently: versioning bugs, timestamp changes, more versioning bugs, ... I\u0027m sure I got some tests in the wrong patches :/","commit_id":"003bffee3abd4bf6cf0e6e389f034ccc8b142ee6"}]}
