diff --git a/src/nox_poetry/poetry.py b/src/nox_poetry/poetry.py
index e56299fe..8f04fd23 100644
--- a/src/nox_poetry/poetry.py
+++ b/src/nox_poetry/poetry.py
@@ -85,6 +85,7 @@ def export(self) -> str:
"--dev",
*[f"--extras={extra}" for extra in self.config.extras],
"--without-hashes",
+ "--with-credentials",
external=True,
silent=True,
stderr=None,
diff --git a/src/nox_poetry/sessions.py b/src/nox_poetry/sessions.py
index 9d451147..f0c84333 100644
--- a/src/nox_poetry/sessions.py
+++ b/src/nox_poetry/sessions.py
@@ -58,6 +58,8 @@ def _split_extras(arg: str) -> Tuple[str, Optional[str]]:
def to_constraint(requirement_string: str, line: int) -> Optional[str]:
"""Convert requirement to constraint."""
+ if requirement_string.startswith("--extra-index-url"):
+ return requirement_string
if any(
requirement_string.startswith(prefix)
for prefix in ("-", "file://", "git+https://", "http://", "https://")
diff --git a/tests/functional/data/simple503/index.html b/tests/functional/data/simple503/index.html
new file mode 100644
index 00000000..9fc90ddc
--- /dev/null
+++ b/tests/functional/data/simple503/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Simple Package Repository
+
+
+
+
+ thispackagedoesnotexist
+
+
+
+
diff --git a/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl b/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl
new file mode 100644
index 00000000..433d59c7
Binary files /dev/null and b/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl differ
diff --git a/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata b/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata
new file mode 100644
index 00000000..1ef725f1
--- /dev/null
+++ b/tests/functional/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata
@@ -0,0 +1,14 @@
+Metadata-Version: 2.1
+Name: thispackagedoesnotexist
+Version: 0.1.0
+Summary: thispackagedoesnotexist
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
diff --git a/tests/functional/data/simple503/thispackagedoesnotexist/index.html b/tests/functional/data/simple503/thispackagedoesnotexist/index.html
new file mode 100644
index 00000000..44a5c5bd
--- /dev/null
+++ b/tests/functional/data/simple503/thispackagedoesnotexist/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ Links for thispackagedoesnotexist
+
+
+
+
+ Links for thispackagedoesnotexist
+
+
+ thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl
+
+
+
+
diff --git a/tests/functional/test_installroot.py b/tests/functional/test_installroot.py
index 4daf07a1..097a9a76 100644
--- a/tests/functional/test_installroot.py
+++ b/tests/functional/test_installroot.py
@@ -1,4 +1,15 @@
"""Functional tests for ``installroot``."""
+import base64
+import os
+import tempfile
+from functools import partial
+from http.server import HTTPServer
+from http.server import SimpleHTTPRequestHandler
+from pathlib import Path
+from threading import Thread
+from typing import Any
+from typing import Tuple
+
import nox_poetry
from tests.functional.conftest import Project
from tests.functional.conftest import list_packages
@@ -79,3 +90,134 @@ def test(session: nox_poetry.Session) -> None:
packages = list_packages(project, test)
assert set(expected) == set(packages)
+
+
+class AuthenticatingSimpleHTTPRequestHandler(SimpleHTTPRequestHandler):
+ """A version of SimpleHTTPRequestHandler that throws a 401 error if the request
+ does not come with the specified username and password sent via basic http
+ authentication. See RFC 7617 for details. This is designed for tests, and does not
+ offer any real protection."""
+
+ def __init__(
+ self,
+ request: Any,
+ client_address: Any,
+ server: Any,
+ directory: Any,
+ username: str,
+ password: str,
+ ):
+ authstring = f"{username}:{password}"
+ self.encoded_authstring = base64.b64encode(authstring.encode("utf-8")).decode(
+ "utf-8"
+ )
+ super().__init__(request, client_address, server, directory=directory)
+
+ def is_authenticated(self) -> bool:
+ if "Authorization" in self.headers:
+ return bool(
+ self.headers["Authorization"] == f"Basic {self.encoded_authstring}"
+ )
+ return False
+
+ def send_auth_error(self) -> None:
+ self.send_response(401)
+ self.send_header("WWW-Authenticate", 'Basic realm="everything"')
+ self.end_headers()
+
+ def do_GET(self) -> None:
+ if self.is_authenticated():
+ super().do_GET()
+ else:
+ self.send_auth_error()
+
+ def do_HEAD(self) -> None:
+ if self.is_authenticated():
+ super().do_HEAD()
+ else:
+ self.send_auth_error()
+
+
+def get_pyproject(address: str) -> str:
+ return f"""\
+[tool.poetry]
+name = "foo"
+version = "0.1.1"
+description = "foo"
+authors = []
+
+[tool.poetry.dependencies]
+"thispackagedoesnotexist" = {{version = "0.1.0", source = "baz"}}
+
+[[tool.poetry.source]]
+name = "baz"
+url = "{address}"
+default = false
+secondary = true
+"""
+
+
+def serve_directory_with_http_and_auth(
+ directory: str, username: str, password: str
+) -> Tuple[HTTPServer, str]:
+ hostname = "localhost"
+ port = 0
+ handler = partial(
+ AuthenticatingSimpleHTTPRequestHandler,
+ directory=directory,
+ username=username,
+ password=password,
+ )
+ httpd = HTTPServer((hostname, port), handler, False)
+ httpd.timeout = 0.5
+
+ httpd.server_bind()
+ address = "http://%s:%d" % (hostname, httpd.server_port)
+
+ httpd.server_activate()
+
+ def serve_forever(httpd: HTTPServer) -> None:
+ with httpd: # to make sure httpd.server_close is called
+ httpd.serve_forever()
+
+ thread = Thread(target=serve_forever, args=(httpd,))
+ thread.setDaemon(True)
+ thread.start()
+
+ return httpd, address
+
+
+def test_dependency_from_private_index(shared_datadir: Path) -> None:
+ input_dir = tempfile.TemporaryDirectory()
+
+ server, address = serve_directory_with_http_and_auth(
+ str(shared_datadir / "simple503"), username="alice", password="password"
+ )
+
+ with open(os.path.join(input_dir.name, "pyproject.toml"), "w") as pyproject_file:
+ pyproject_file.write(get_pyproject(address))
+ (Path(input_dir.name) / "foo").mkdir()
+ (Path(input_dir.name) / "foo" / "__init__.py").touch()
+
+ @nox_poetry.session
+ def test(session: nox_poetry.Session) -> None:
+ session.run_always(
+ "poetry",
+ "config",
+ "http-basic.baz",
+ "alice",
+ "password",
+ external=True,
+ silent=True,
+ stderr=None,
+ )
+ session.run_always("poetry", "lock")
+ session.poetry.installroot()
+
+ project = Project(Path(input_dir.name))
+
+ try:
+ run_nox_with_noxfile(project, [test], [nox_poetry])
+
+ finally:
+ server.shutdown()
diff --git a/tests/unit/data/simple503/index.html b/tests/unit/data/simple503/index.html
new file mode 100644
index 00000000..9fc90ddc
--- /dev/null
+++ b/tests/unit/data/simple503/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Simple Package Repository
+
+
+
+
+ thispackagedoesnotexist
+
+
+
+
diff --git a/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl b/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl
new file mode 100644
index 00000000..433d59c7
Binary files /dev/null and b/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl differ
diff --git a/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata b/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata
new file mode 100644
index 00000000..1ef725f1
--- /dev/null
+++ b/tests/unit/data/simple503/thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl.metadata
@@ -0,0 +1,14 @@
+Metadata-Version: 2.1
+Name: thispackagedoesnotexist
+Version: 0.1.0
+Summary: thispackagedoesnotexist
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
diff --git a/tests/unit/data/simple503/thispackagedoesnotexist/index.html b/tests/unit/data/simple503/thispackagedoesnotexist/index.html
new file mode 100644
index 00000000..ceaef7f2
--- /dev/null
+++ b/tests/unit/data/simple503/thispackagedoesnotexist/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ Links for thispackagedoesnotexist
+
+
+
+
+ Links for thispackagedoesnotexist
+
+
+ thispackagedoesnotexist-0.1.0-py2.py3-none-any.whl
+
+
+
+
diff --git a/tests/unit/test_poetry.py b/tests/unit/test_poetry.py
index d7ce94f3..c6451d7f 100644
--- a/tests/unit/test_poetry.py
+++ b/tests/unit/test_poetry.py
@@ -1,7 +1,15 @@
"""Unit tests for the poetry module."""
+import os
+import pdb
+import tempfile
+from functools import partial
+from http.server import HTTPServer
+from http.server import SimpleHTTPRequestHandler
from pathlib import Path
+from threading import Thread
from typing import Any
from typing import Dict
+from typing import Tuple
import nox._options
import nox.command
@@ -61,3 +69,78 @@ def _run(*args: Any, **kwargs: Any) -> str:
output = poetry.Poetry(session).export()
assert output == requirements
+
+
+def get_pyproject(address: str) -> str:
+ return f"""\
+[tool.poetry]
+name = "foo"
+version = "0.1.0"
+description = "foo"
+authors = []
+
+[tool.poetry.dependencies]
+"thispackagedoesnotexist" = {{version = "0.1.0", source = "baz"}}
+
+[[tool.poetry.source]]
+name = "baz"
+url = "{address}"
+default = false
+secondary = true
+"""
+
+
+def serve_directory_with_http(directory: str) -> Tuple[HTTPServer, str]:
+ hostname = "localhost"
+ port = 0
+ handler = partial(SimpleHTTPRequestHandler, directory=directory)
+ httpd = HTTPServer((hostname, port), handler, False)
+ httpd.timeout = 0.5
+
+ httpd.server_bind()
+ address = "http://%s:%d" % (hostname, httpd.server_port)
+
+ httpd.server_activate()
+
+ def serve_forever(httpd: HTTPServer) -> None:
+ with httpd: # to make sure httpd.server_close is called
+ httpd.serve_forever()
+
+ thread = Thread(target=serve_forever, args=(httpd,))
+ thread.setDaemon(True)
+ thread.start()
+
+ return httpd, address
+
+
+@nox.session
+def test_export_with_source_credentials(
+ session: nox.Session, shared_datadir: Path
+) -> None:
+ input_dir = tempfile.TemporaryDirectory()
+
+ server, address = serve_directory_with_http(str(shared_datadir / "simple503"))
+
+ with open(os.path.join(input_dir.name, "pyproject.toml"), "w") as pyproject_file:
+ pyproject_file.write(get_pyproject(address))
+
+ cwd = os.getcwd()
+ try:
+ os.chdir(input_dir.name)
+ session.run_always(
+ "poetry",
+ "config",
+ "http-basic.baz",
+ "alice",
+ "password",
+ external=True,
+ silent=True,
+ stderr=None,
+ )
+ test_poetry = poetry.Poetry(session)
+ resources_file = test_poetry.export()
+ expected_index = "http://alice:password@" + address.lstrip("http://")
+ assert f"--extra-index-url {expected_index}" in resources_file
+ finally:
+ os.chdir(cwd)
+ server.shutdown()
diff --git a/tests/unit/test_sessions.py b/tests/unit/test_sessions.py
index 13516c52..a4a1e43a 100644
--- a/tests/unit/test_sessions.py
+++ b/tests/unit/test_sessions.py
@@ -161,7 +161,10 @@ def test_session_build_package(proxy: nox_poetry.Session) -> None:
'regex==2020.10.28; python_version == "3.5"',
),
("-e ../lib/foo", ""),
- ("--extra-index-url https://example.com/pypi/simple", ""),
+ (
+ "--extra-index-url https://example.com/pypi/simple",
+ "--extra-index-url https://example.com/pypi/simple",
+ ),
(
dedent(
"""
@@ -170,7 +173,14 @@ def test_session_build_package(proxy: nox_poetry.Session) -> None:
boltons==20.2.1
"""
),
- "boltons==20.2.1",
+ dedent(
+ """
+ --extra-index-url https://example.com/pypi/simple
+ boltons==20.2.1
+ """
+ )
+ .lstrip("\n")
+ .rstrip("\n"),
),
],
)