==========
setuptools
==========

One of the most important features of MYPYPI is to act like the PyPi server.
That means setuptools is able to upload packages to MYPYPI.
This allows us to use MYPYPI as a private, closed source package storage.
Permissions can be set on the site and package level.


Checking here parts that are used by setuptools

- verify
- register
- upload
- simple index (without authorization, for index=)
- private simple index (with authorization, for index=)
- eggs list (without authorization, for find-links=)
- private eggs list (with authorization, for find-links=)

We need two users to check:

A user who should see everything.

  >>> from zope.testbrowser.testing import Browser
  >>> manager = Browser()
  >>> manager.addHeader('Authorization', 'Basic mgr:mgrpw')
  >>> manager.handleErrors = False

  >>> manager.open('http://localhost')


A user that is not logged on -- can just see public stuff.

  >>> anyone = Browser()
  >>> anyone.handleErrors = False

  >>> anyone.open('http://localhost/simple')

  >>> from zope.app.testing import functional

Verify
======

Verify package on server.

This is the payload:

  >>> VERIFY_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="license"
  ...
  ... ZPL 2.1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="metadata_version"
  ...
  ... 1.0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="author"
  ...
  ... Projekt01 GmbH, 6330 Cham, switzerland
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="home_page"
  ...
  ... https://pypi.org/pypi/MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... verify
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="download_url"
  ...
  ... UNKNOWN
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="summary"
  ...
  ... My Python Package Index (Standalone Server)
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="author_email"
  ...
  ... dev@projekt01.ch
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="platform"
  ...
  ... UNKNOWN
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="keywords"
  ...
  ... python buildout package index server egg pypi mirror private
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Development Status :: 4 - Beta
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Environment :: Web Environment
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Intended Audience :: Developers
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... License :: OSI Approved :: Zope Public License
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Programming Language :: Python
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Natural Language :: English
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Operating System :: OS Independent
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Topic :: Internet :: WWW/HTTP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Framework :: Zope3
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="description"
  ...
  ... This package provides a private python package index server based on Zope 3.
  ...
  ...
  ... =======
  ... CHANGES
  ... =======
  ...
  ...
  ... Version 0.5.0dev (unreleased)
  ... -----------------------------
  ...
  ... - Initial Release
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the header:

  >>> verify = """POST / HTTP/1.1
  ... Content-Length: 3082
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

Call the server:

  >>> response = functional.HTTPCaller()(verify+VERIFY_data)
  >>> response.getStatus()
  200
  >>> print str(response)
  HTTP/1.1 200 Ok
  Content-Length: 2
  Content-Type: text/plain
  <BLANKLINE>
  OK


Register
========

Setuptools is that crazy that it even uses a fixed MIME separator.
So we can put here the complete POST data, it won't change.

  >>> REGISTER_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="license"
  ...
  ... ZPL 2.1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="metadata_version"
  ...
  ... 1.0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="author"
  ...
  ... Projekt01 GmbH, 6330 Cham, switzerland
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="home_page"
  ...
  ... https://pypi.org/pypi/MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... submit
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="download_url"
  ...
  ... UNKNOWN
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="summary"
  ...
  ... My Python Package Index (Standalone Server)
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="author_email"
  ...
  ... dev@projekt01.ch
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="platform"
  ...
  ... UNKNOWN
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="keywords"
  ...
  ... python buildout package index server egg pypi mirror private
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Development Status :: 4 - Beta
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Environment :: Web Environment
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Intended Audience :: Developers
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... License :: OSI Approved :: Zope Public License
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Programming Language :: Python
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Natural Language :: English
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Operating System :: OS Independent
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Topic :: Internet :: WWW/HTTP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="classifiers"
  ...
  ... Framework :: Zope3
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="description"
  ...
  ... This package provides a private python package index server based on Zope 3.
  ...
  ...
  ... =======
  ... CHANGES
  ... =======
  ...
  ...
  ... Version 0.5.0dev (unreleased)
  ... -----------------------------
  ...
  ... - Initial Release
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--"""

No authorization in the first request.
setuptools does the same. It first bangs head without authorization.

  >>> register1 = """POST / HTTP/1.1
  ... Content-Length: 3082
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

No authorization -- 401 Unauthorized is the response.
We could check the body, but who cares it will tell the same story.

  >>> response = functional.HTTPCaller()(register1+REGISTER_data)
  >>> response.getStatus()
  401

In turn, the package must not be registered.

  >>> manager.open('http://localhost')

  >>> 'MYPACKAGE' in manager.contents
  False

Authorize in the second request.

  >>> register2 = """POST / HTTP/1.1
  ... Content-Length: 3082
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

Now we get a green light.

  >>> response = functional.HTTPCaller()(register2+REGISTER_data)
  >>> response.getStatus()
  200
  >>> print str(response)
  HTTP/1.1 200 Ok
  Content-Length: 52
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Created Package: MYPACKAGE
  Created Release: 0.5.0dev

And in turn the package must be registered:

  >>> manager.open('http://localhost')
  >>> print manager.contents
  <!DOCTYPE html...
  ...X...Mirror...Published...Package...Source...Created...Modified...
  ...No...http://localhost/MYPACKAGE/releases.html...MYPACKAGE...
  <BLANKLINE>

Let's check the contents:

  >>> manager.getLink('MYPACKAGE').click()
  >>> print manager.contents
  <!DOCTYPE html...
  ...X...Mirror...Published...Release...Source...Created...Modified...
  ...No...http://localhost/MYPACKAGE/0.5.0dev/files.html...0.5.0dev...
  <BLANKLINE>

Let's check the files:

  >>> manager.getLink('0.5.0dev').click()

There are none yet.

  >>> manager.getLink('Edit').click()
  >>> print manager.contents
  <!DOCTYPE html...
  ...<legend>Edit Local Release</legend>...
  ...Published...checked="checked"...yes...no...
  ...Is published...no...
  ...Version...0.5.0dev...
  ...Author...Projekt01 GmbH, 6330 Cham, switzerland...
  ...Author Email...dev@projekt01.ch...
  ...Classifiers...
  ...Natural Language :: English...
  ...Operating System :: OS Independent...
  ...Programming Language :: Python...
  ...Framework :: Zope3...
  ...Topic :: Internet :: WWW/HTTP...
  ...Development Status :: 4 - Beta...
  ...Intended Audience :: Developers...
  ...License :: OSI Approved :: Zope Public License...
  ...Environment :: Web Environment...
  ...Description...This package provides a private python package index server based on Zope 3...
  =======
  CHANGES
  =======
  <BLANKLINE>
  <BLANKLINE>
  Version 0.5.0dev (unreleased)
  -----------------------------
  <BLANKLINE>
  - Initial Release...
  ...Download URL...UNKNOWN...
  ...Homepage...https://pypi.org/pypi/MYPACKAGE...
  ...Keywords...python buildout package index server egg pypi mirror private...
  ...License...ZPL 2.1...
  ...Maintainer...
  ...Maintainer Email...
  ...Obsoletes...
  ...Pypi hidden...value="1"...
  ...Pypi ordering...value="10"...
  ...Platform...UNKNOWN...
  ...Protocol Version...
  ...Provides...
  ...Requires...
  ...Stable version...
  ...Summary...My Python Package Index (Standalone Server)...
  <BLANKLINE>

The simple index is totally empty:

  >>> manager.open('http://localhost/simple')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>

  >>> anyone.open('http://localhost/simple')
  >>> print anyone.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>

Eggs page is also empty:

  >>> manager.open('http://localhost/eggs')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>

  >>> anyone.open('http://localhost/eggs')
  >>> print anyone.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>


Upload
======

This is the upload payload:

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)

As expected we get an Unauthorized:

  >>> response.getStatus()
  401

Now try again with authorization:

  >>> UPLOAD2 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

Shoot the request:

  >>> response = functional.HTTPCaller()(UPLOAD2+UPLOAD_data, handle_errors=False)

Now we're OK:

  >>> response.getStatus()
  200

The file should be on the server:

  >>> print str(response)
  HTTP/1.1 200 Ok
  Content-Length: 70
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Updated Release: 0.5.0dev
  Created Release File: MYPACKAGE-0.5.0dev.zip

Check via the browser:

  >>> manager.open('http://localhost')
  >>> print manager.contents
  <!DOCTYPE html...
  ...X...Mirror...Published...Package...Source...Created...Modified...
  ...No...http://localhost/MYPACKAGE/releases.html...MYPACKAGE...
  <BLANKLINE>

Let's check the contents:

  >>> manager.getLink('MYPACKAGE').click()
  >>> print manager.contents
  <!DOCTYPE html...
  ...X...Mirror...Published...Release...Source...Created...Modified...
  ...No...http://localhost/MYPACKAGE/0.5.0dev/files.html...0.5.0dev...
  <BLANKLINE>

Let's check the files:

  >>> manager.getLink('0.5.0dev').click()
  >>> print manager.contents
  <!DOCTYPE html...
  ...X...Mirror...Published...Name...Source...Created...Modified...
  ...No...(Yes)...http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip...MYPACKAGE-0.5.0dev.zip...
  <BLANKLINE>

The simple index lists now the package:

  >>> manager.open('http://localhost/simple')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/simple/MYPACKAGE">MYPACKAGE</a>
  </body>
  </html>
  <BLANKLINE>

Check the package contents:

  >>> manager.getLink('MYPACKAGE').click()
  >>> print manager.contents
  <html>
  <head><title>MYPACKAGE</title>
  </head>
  <body>
  <h1>MYPACKAGE</h1>
  <a href="http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip">MYPACKAGE-0.5.0dev.zip</a>
  </body>
  </html>
  <BLANKLINE>

And download the package file itself:

  >>> manager.getLink('MYPACKAGE-0.5.0dev.zip').click()
  >>> print manager.contents
  PK--MYPACKAGE--ZIP


The simple index does not list the package for anyone:

  >>> anyone.open('http://localhost/simple')
  >>> print anyone.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>

Private index works for the manager:

  >>> manager.open('http://localhost/private')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/simple/MYPACKAGE">MYPACKAGE</a>
  </body>
  </html>
  <BLANKLINE>

Anyone bangs head on authorization on the private page:

  >>> anyone.open('http://localhost/private')
  Traceback (most recent call last):
  ...
  Unauthorized: (<...SimpleIndex object at ...>, 'browserDefault', 'mypypi.View')

The eggs page lists also the file:
NOTE that here the files are listed, not the packages pointing to a next page.

  >>> manager.open('http://localhost/eggs')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip">MYPACKAGE-0.5.0dev.zip</a>
  </body>
  </html>
  <BLANKLINE>

But not for anyone:

  >>> anyone.open('http://localhost/eggs')
  >>> print anyone.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <BLANKLINE>
  </body>
  </html>
  <BLANKLINE>

Private eggs page lists the file too:

  >>> manager.open('http://localhost/privateEggs')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip">MYPACKAGE-0.5.0dev.zip</a>
  </body>
  </html>
  <BLANKLINE>

Anyone bangs head on authorization on the private eggs page:

  >>> anyone.open('http://localhost/privateEggs')
  Traceback (most recent call last):
  ...
  Unauthorized: (<...Eggs object at ...>, 'browserDefault', 'mypypi.View')


Upload again. This is a very bad situation, because once published files
should never be changed but we support this case as pypi does it too.

Shoot the request again:

  >>> response = functional.HTTPCaller()(UPLOAD2+UPLOAD_data, handle_errors=False)

We're OK the only notice is that we ``Updated`` the file:

  >>> print str(response)
  HTTP/1.1 200 Ok
  Content-Length: 70
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Updated Release: 0.5.0dev
  Updated Release File: MYPACKAGE-0.5.0dev.zip


Upload one more file.

This is the upload payload:

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... 38d1c0a86aabf51f6340a949a77f03ee
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-1.0.0.zip"
  ...
  ... PK--MYPACKAGE--NEW--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 1.0.0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

  >>> response = functional.HTTPCaller()(UPLOAD2+UPLOAD_data, handle_errors=False)

We're OK the only notice is that we ``Updated`` the file:

  >>> print str(response)
  HTTP/1.1 200 Ok
  Content-Length: 64
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Created Release: 1.0.0
  Created Release File: MYPACKAGE-1.0.0.zip

Check the eggs page.

  >>> manager.open('http://localhost/eggs')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip">MYPACKAGE-0.5.0dev.zip</a><br />
  <a href="http://localhost/MYPACKAGE/1.0.0/MYPACKAGE-1.0.0.zip">MYPACKAGE-1.0.0.zip</a>
  </body>
  </html>
  <BLANKLINE>

Private eggs page lists the file too:

  >>> manager.open('http://localhost/privateEggs')
  >>> print manager.contents
  <html>
  <head><title>PYPISite</title>
  </head>
  <body>
  <h1>PYPISite</h1>
  <a href="http://localhost/MYPACKAGE/0.5.0dev/MYPACKAGE-0.5.0dev.zip">MYPACKAGE-0.5.0dev.zip</a><br />
  <a href="http://localhost/MYPACKAGE/1.0.0/MYPACKAGE-1.0.0.zip">MYPACKAGE-1.0.0.zip</a>
  </body>
  </html>
  <BLANKLINE>


Now that all worked because we were logged in as manager.
As next we will have to check permissions.

Edge cases
==========

``name`` missing
~~~~~~~~~~~~~~~~

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 'name' is required but missing
  Content-Length: 30
  Content-Type: text/plain
  <BLANKLINE>
  'name' is required but missing


``version`` missing
~~~~~~~~~~~~~~~~~~~

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 'version' is required but missing
  Content-Length: 33
  Content-Type: text/plain
  <BLANKLINE>
  'version' is required but missing

``content`` missing
~~~~~~~~~~~~~~~~~~~

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 Content is required but missing
  Content-Length: 31
  Content-Type: text/plain
  <BLANKLINE>
  Content is required but missing

``filetype`` missing
~~~~~~~~~~~~~~~~~~~~

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 Filetype is required but missing
  Content-Length: 32
  Content-Type: text/plain
  <BLANKLINE>
  Filetype is required but missing


bad ``filename``
~~~~~~~~~~~~~~~~~~~~

(extension)

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zzz"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 Invalid file given: MYPACKAGE-0.5.0dev.zzz
  Content-Length: 42
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Invalid file given: MYPACKAGE-0.5.0dev.zzz

bad ``filename``
~~~~~~~~~~~~~~~~~~~~

(path in filename)

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... a0da9fa5b08a7acd4f850283123d60c0
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="dist/MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 Invalid file given: dist/MYPACKAGE-0.5.0dev.zip
  Content-Length: 47
  Content-Type: text/plain;charset=utf-8
  <BLANKLINE>
  Invalid file given: dist/MYPACKAGE-0.5.0dev.zip

bad md5_digest
~~~~~~~~~~~~~~

(extension)

  >>> UPLOAD_data = """----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="comment"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="protcol_version"
  ...
  ... 1
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="md5_digest"
  ...
  ... xxxxxxxxxxxxxxxxxxxxxx
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="filetype"
  ...
  ... sdist
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name=":action"
  ...
  ... file_upload
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="pyversion"
  ...
  ...
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="name"
  ...
  ... MYPACKAGE
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="content";filename="MYPACKAGE-0.5.0dev.zip"
  ...
  ... PK--MYPACKAGE--ZIP
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254
  ... Content-Disposition: form-data; name="version"
  ...
  ... 0.5.0dev
  ... ----------------GHSKFJDLGDS7543FJKLFHRE75642756743254--
  ... """

This is the upload header:

  >>> UPLOAD1 = """POST / HTTP/1.1
  ... Content-Length: 1202
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=--------------GHSKFJDLGDS7543FJKLFHRE75642756743254; charset=utf-8
  ... Host: localhost
  ... User-agent: Python-urllib/2.4
  ...
  ... """

setuptools always uses authorization in case of upload, but we'll try now without
to check the app.

  >>> response = functional.HTTPCaller()(UPLOAD1+UPLOAD_data)
  >>> print str(response)
  HTTP/1.1 400 MD5 digest does not match with the uploaded file
  Content-Length: 48
  Content-Type: text/plain
  <BLANKLINE>
  MD5 digest does not match with the uploaded file