diff --git a/nova/tests/fixtures.py b/nova/tests/fixtures.py
index 5e16c545b779..ce66ddb77411 100644
--- a/nova/tests/fixtures.py
+++ b/nova/tests/fixtures.py
@@ -874,7 +874,7 @@ class OSAPIFixture(fixtures.Fixture):
 
     def __init__(self, api_version='v2',
                  project_id='6f70656e737461636b20342065766572',
-                 use_project_id_in_urls=False):
+                 use_project_id_in_urls=False, stub_keystone=True):
         """Constructor
 
         :param api_version: the API version that we're interested in
@@ -883,11 +883,14 @@ class OSAPIFixture(fixtures.Fixture):
         :param project_id: the project id to use on the API.
         :param use_project_id_in_urls: If True, act like the "endpoint" in the
             "service catalog" has the legacy format including the project_id.
+        :param stub_keystone: If True, stub keystonemiddleware and
+            NovaKeystoneContext to simulate (but not perform) real auth.
         """
         super(OSAPIFixture, self).__init__()
         self.api_version = api_version
         self.project_id = project_id
         self.use_project_id_in_urls = use_project_id_in_urls
+        self.stub_keystone = stub_keystone
 
     def setUp(self):
         super(OSAPIFixture, self).setUp()
@@ -903,22 +906,8 @@ class OSAPIFixture(fixtures.Fixture):
         }
         self.useFixture(ConfPatcher(**conf_overrides))
 
-        # Stub out authentication middleware
-        # TODO(efried): Use keystonemiddleware.fixtures.AuthTokenFixture
-        self.useFixture(fixtures.MockPatch(
-            'keystonemiddleware.auth_token.filter_factory',
-            return_value=lambda _app: _app))
-
-        # Stub out context middleware
-        def fake_ctx(env, **kwargs):
-            user_id = env['HTTP_X_AUTH_USER']
-            project_id = env['HTTP_X_AUTH_PROJECT_ID']
-            is_admin = user_id == 'admin'
-            return context.RequestContext(
-                user_id, project_id, is_admin=is_admin, **kwargs)
-
-        self.useFixture(fixtures.MonkeyPatch(
-            'nova.api.auth.NovaKeystoneContext._create_context', fake_ctx))
+        if self.stub_keystone:
+            self._stub_keystone()
 
         # Turn off manipulation of socket_options in TCPKeepAliveAdapter
         # to keep wsgi-intercept happy. Replace it with the method
@@ -950,6 +939,24 @@ class OSAPIFixture(fixtures.Fixture):
         # the fixture.
         self.app = app
 
+    def _stub_keystone(self):
+        # Stub out authentication middleware
+        # TODO(efried): Use keystonemiddleware.fixtures.AuthTokenFixture
+        self.useFixture(fixtures.MockPatch(
+            'keystonemiddleware.auth_token.filter_factory',
+            return_value=lambda _app: _app))
+
+        # Stub out context middleware
+        def fake_ctx(env, **kwargs):
+            user_id = env['HTTP_X_AUTH_USER']
+            project_id = env['HTTP_X_AUTH_PROJECT_ID']
+            is_admin = user_id == 'admin'
+            return context.RequestContext(
+                user_id, project_id, is_admin=is_admin, **kwargs)
+
+        self.useFixture(fixtures.MonkeyPatch(
+            'nova.api.auth.NovaKeystoneContext._create_context', fake_ctx))
+
 
 class OSMetadataServer(fixtures.Fixture):
     """Create an OS Metadata API server as a fixture.
diff --git a/nova/tests/functional/api_sample_tests/test_versions.py b/nova/tests/functional/api_sample_tests/test_versions.py
index 166195769f5a..373b87c002ea 100644
--- a/nova/tests/functional/api_sample_tests/test_versions.py
+++ b/nova/tests/functional/api_sample_tests/test_versions.py
@@ -12,33 +12,64 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import ddt
+import fixtures
+import webob
 
 from nova.api.openstack import api_version_request as avr
 from nova.tests.functional.api_sample_tests import api_sample_base
 
 
+@ddt.ddt
 class VersionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
+    """Validate that proper version documents can be fetched without auth."""
+
+    # Here we want to avoid stubbing keystone middleware. That will cause
+    # "real" keystone middleware to run (and fail) if it's in the pipeline.
+    # (The point of this test is to prove we do version discovery through
+    # pipelines that *don't* authenticate.)
+    STUB_KEYSTONE = False
+
     sample_dir = 'versions'
     _use_project_id = False
     # NOTE(gmann): Setting empty scenario for 'version' API testing
     # as those does not send request on particular endpoint and running
     # its tests alone is enough.
     scenarios = []
-    max_api_version = avr.max_api_version().get_string()
+    max_api_version = {'max_api_version': avr.max_api_version().get_string()}
 
-    def test_versions_get(self):
-        response = self._do_get('', strip_version=True)
-        self._verify_response('versions-get-resp',
-                              {'max_api_version': self.max_api_version},
+    def setUp(self):
+        super(VersionsSampleJsonTest, self).setUp()
+        # Version documents are supposed to be available without auth, so make
+        # the auth middleware "fail" authentication.
+        self.useFixture(fixtures.MockPatch(
+            # [api]auth_strategy is set to noauth2 by the ConfFixture
+            'nova.api.openstack.auth.NoAuthMiddlewareBase.base_call',
+            return_value=webob.Response(status=401)))
+
+    def _get(self, url):
+        return self._do_get(
+            url,
+            # Since we're explicitly getting discovery endpoints, strip the
+            # automatic /v2[.1] added by the fixture.
+            strip_version=True)
+
+    @ddt.data('', '/')
+    def test_versions_get_base(self, url):
+        response = self._get(url)
+        self._verify_response('versions-get-resp', self.max_api_version,
                               response, 200, update_links=False)
 
-    def test_versions_get_v2(self):
-        response = self._do_get('/v2', strip_version=True)
-        self._verify_response('v2-version-get-resp', {},
-                              response, 200, update_links=False)
-
-    def test_versions_get_v21(self):
-        response = self._do_get('/v2.1', strip_version=True)
-        self._verify_response('v21-version-get-resp',
-                              {'max_api_version': self.max_api_version},
-                              response, 200, update_links=False)
+    @ddt.data(('/v2', 'v2-version-get-resp', {}),
+              ('/v2/', 'v2-version-get-resp', {}),
+              ('/v2.1', 'v21-version-get-resp', max_api_version),
+              ('/v2.1/', 'v21-version-get-resp', max_api_version))
+    @ddt.unpack
+    def test_versions_get_versioned(self, url, tplname, subs):
+        response = self._get(url)
+        # TODO(efried): This is bug 1845530 whereby we try to authenticate at
+        #  the versioned discovery endpoint.
+        self.assertEqual(401, response.status_code)
+        # TODO(efried): Uncomment when bug 1845530 is resolved
+        # self._verify_response(tplname, subs, response, 200,
+        #                       update_links=False)
diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py
index 4215e59df1c3..0c9a7df0010f 100644
--- a/nova/tests/functional/integrated_helpers.py
+++ b/nova/tests/functional/integrated_helpers.py
@@ -362,6 +362,10 @@ class _IntegratedTestBase(test.TestCase, InstanceHelperMixin):
     # This indicates whether to include the project ID in the URL for API
     # requests through OSAPIFixture. Overridden by subclasses.
     _use_project_id = False
+    # Override this in subclasses to avoid stubbing keystonemiddleware and
+    # NovaKeystoneContext, thus making those middlewares behave as they would
+    # in real life (i.e. try to do real authentication).
+    STUB_KEYSTONE = True
 
     def setUp(self):
         super(_IntegratedTestBase, self).setUp()
@@ -398,7 +402,8 @@ class _IntegratedTestBase(test.TestCase, InstanceHelperMixin):
         self.api_fixture = self.useFixture(
             nova_fixtures.OSAPIFixture(
                 api_version=self.api_major_version,
-                use_project_id_in_urls=self._use_project_id))
+                use_project_id_in_urls=self._use_project_id,
+                stub_keystone=self.STUB_KEYSTONE))
 
         # if the class needs to run as admin, make the api endpoint
         # the admin, otherwise it's safer to run as non admin user.