os.path.join is used to join parts of URLs
For example:
- [/girder/api/api_main.py#L34](https://github.com/girder/girder/blob/7e743c25da7e5fe1984bf53e87d1a33fb7ef9b7c/girder/api/api_main.py#L34)
- [/plugins/oauth/server/rest.py#L65](https://github.com/girder/girder/blob/7e743c25da7e5fe1984bf53e87d1a33fb7ef9b7c/plugins/oauth/server/rest.py#L65)
- [/girder/utility/s3_assetstore_adapter.py#L92](https://github.com/girder/girder/blob/6d8d431ca8ba9449da4ad3f73b838ed6bf72e033/girder/utility/s3_assetstore_adapter.py#L92)
- [/girder/utility/s3_assetstore_adapter.py#L136](https://github.com/girder/girder/blob/6d8d431ca8ba9449da4ad3f73b838ed6bf72e033/girder/utility/s3_assetstore_adapter.py#L136)

Causes tests to fail on Windows.

diff --git a/girder/api/api_main.py b/girder/api/api_main.py
index d53f42d68..7f28465d1 100644
--- a/girder/api/api_main.py
+++ b/girder/api/api_main.py
@@ -18,7 +18,6 @@
 ###############################################################################
 
 import cherrypy
-import os
 
 from . import describe
 from v1 import assetstore, file, collection, folder, group, item, resource,\\
@@ -31,7 +30,7 @@ class ApiDocs():
     def GET(self):
         # Since we only have v1 right now, just redirect to the v1 page.
         # If we get more versions, this should show an index of them.
-        raise cherrypy.HTTPRedirect(os.path.join(cherrypy.url(), 'v1'))
+        raise cherrypy.HTTPRedirect(cherrypy.url() + '/v1')
 
 
 def addApiToNode(node):
diff --git a/girder/utility/s3_assetstore_adapter.py b/girder/utility/s3_assetstore_adapter.py
index d6f3b3346..9869a6abe 100644
--- a/girder/utility/s3_assetstore_adapter.py
+++ b/girder/utility/s3_assetstore_adapter.py
@@ -23,7 +23,6 @@ import cherrypy
 import hashlib
 import hmac
 import json
-import os
 import time
 import urllib
 import uuid
@@ -89,7 +88,8 @@ class S3AssetstoreAdapter(AbstractAssetstoreAdapter):
                                    aws_secret_access_key=doc['secret'])
             bucket = conn.lookup(bucket_name=doc['bucket'], validate=False)
             testKey = boto.s3.key.Key(
-                bucket=bucket, name=os.path.join(doc['prefix'], 'test'))
+                bucket=bucket, name='/'.join(
+                    filter(None, (doc['prefix'], 'test'))))
             testKey.set_contents_from_string('')
         except:
             logger.exception('S3 assetstore validation exception')
@@ -133,8 +133,8 @@ class S3AssetstoreAdapter(AbstractAssetstoreAdapter):
 
         uid = uuid.uuid4().hex
         expires = int(time.time() + self.HMAC_TTL)
-        key = os.path.join(self.assetstore.get('prefix', ''),
-                           uid[0:2], uid[2:4], uid)
+        key = '/'.join(filter(None, (self.assetstore.get('prefix', ''),
+                       uid[0:2], uid[2:4], uid)))
         path = '/{}/{}'.format(self.assetstore['bucket'], key)
         canonical, allHeaders = self._getRequestHeaders(upload)
 
diff --git a/plugins/oauth/server/rest.py b/plugins/oauth/server/rest.py
index 6f6a00cc1..193a0a2e9 100644
--- a/plugins/oauth/server/rest.py
+++ b/plugins/oauth/server/rest.py
@@ -62,8 +62,8 @@ class OAuth(Resource):
         if clientId is None:
             raise Exception('No Google client ID setting is present.')
 
-        callbackUrl = os.path.join(
-            os.path.dirname(cherrypy.url()), 'google', 'callback')
+        callbackUrl = '/'.join((
+            os.path.dirname(cherrypy.url()), 'google', 'callback'))
 
         query = urllib.urlencode({
             'response_type': 'code',
['plugins/oauth/server/rest.py', 'girder/api/api_main.py', 'girder/utility/s3_assetstore_adapter.py']