# -*- coding: utf-8 -*-
"""
Unit and regression tests for purx.

This MUST NOT run on a production site.  It may introduce spurious
records into the VO registry or delete real ones.

The tests assume AUTHORITY=purx and an empty purx.sources table.
"""

import contextlib
import datetime
import re
import os
import time
os.environ["GAVO_OOTTEST"] = "dontcare"

from lxml import etree
import requests

from gavo import api
from gavo import utils
from gavo.base import osinter
from gavo.helpers import testhelpers
from gavo.protocols import oaiclient
from gavo.registry import oaiinter

RD = api.getRD("purx/q")

enrollment, _ =  utils.loadPythonModule(
	os.path.join(RD.resdir, "res/enrollment"))
purxoai, _ =  utils.loadPythonModule(
	os.path.join(RD.resdir, "res/purxoai"))




REC_SOURCE = """
<ri:Resource status="active" xsi:type="vstd:Standard" updated="2015-07-21T07:07:39Z" created="2015-07-14T07:49:00Z"><title>The Global TAP Schema GloTS</title><identifier>ivo://org.gavo.dc/std/glots</identifier>
<altIdentifier>doi://10.5072/7a6KXm7hR9I6iVE46DEaRA</altIdentifier><curation><publisher>The GAVO DC team</publisher><creator><name>Markus Demleitner</name></creator><date role="updated">2015-07-21T07:07:39</date><contact><name>GAVO Data Center Team</name><address>Mönchhofstrasse 12-14, D-69120 Heidelberg</address><email>operator@ari.uni-heidelberg.de</email><telephone>++49 6221 54 1837</telephone></contact></curation><content><subject>Virtual observatory</subject><subject>Standards</subject><subject>TAP_SCHEMA</subject><description> The Global TAP schema is an attempt at a simple way to present a
union of all TAP_SCHEMA instances globally, where right now GloTS only
contains tables and columns. It is primarily intended as a quick (and
relatively dirty) way for data collection discovery until other means
are ready.</description><referenceURL>http://ivoa.net/documents/discoveringcollections</referenceURL></content><endorsedVersion status="note" use="preferred">1.0</endorsedVersion><key><name>tables-1.0</name><description>The data model (as used in TAPRegExt's dataModel element), consisting
of glots.services, glots.tables, and glots.columns.</description></key></ri:Resource>
"""


SAMPLE_RECORD = oaiclient._addCanonicalNSDecls(REC_SOURCE).encode("utf-8")

SIAP_RECORD = """<?xml version="1.0" encoding="utf-8"?>
<oai:OAI-PMH xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:oai="http://www.openarchives.org/OAI/2.0/" >
  <oai:responseDate>2012-02-23T10:48:41.1343802-05:00</oai:responseDate>
  <oai:request verb="ListRecords" metadataPrefix="ivo_vor" from="2012-02-02T13:17:28Z">http://nvo.stsci.edu/vor10/oai.aspx</oai:request>
  <oai:ListRecords>
      <oai:record>
      <oai:header>
        <oai:identifier>ivo://mast.stsci/siap/xmm-om</oai:identifier>
        <oai:datestamp>2012-02-02T18:36:16Z</oai:datestamp>
				<oai:setSpec>ivo_managed</oai:setSpec>
      </oai:header>
      <oai:metadata>
        <ri:Resource xmlns:ri="http://www.ivoa.net/xml/RegistryInterface/v1.0" xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.1" xmlns:stc="http://www.ivoa.net/xml/STC/stc-v1.30.xsd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sia="http://www.ivoa.net/xml/SIA/v1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vs:CatalogService" status="active" created="2012-02-02T18:36:16Z" updated="2012-02-02T18:36:16Z">
          <validationLevel validatedBy="ivo://archive.stsci.edu/nvoregistry">2</validationLevel>
          <title>X-ray Multi-Mirror (XMM) Optical Monitor images</title>
          <shortName>XMM-OM</shortName>
          <identifier>ivo://mast.stsci/siap/xmm-om</identifier>
          <curation>
            <publisher>         MAST      </publisher>
            <creator>
              <name>ESA</name>
            </creator>
            <version>1.0</version>
            <contact>
              <name>Archive Branch, STScI</name>
              <email>archive@stsci.edu</email>
            </contact>
          </curation>
          <content>
            <subject>Optical  Astronomy</subject>
            <subject>Ultraviolet Astronomy</subject>
            <description>
The Newton X-ray Multi-Mirror Mission (XMM) was launched in December, 1999 with a projected
lifetime of 10 years. Although intended primarily as an X-ray observatory, XMM included a small
but powerful optical/UV 30 cm telescope co-aligned with the X-ray telescopes for contemporaneous observations.
The modified Ritchey-Chretien telescope is capable of detecting 24th magnitude sources in its 17 arcmin field of view.
It provides images in the 160-600nm wave band with 1 arcsec resolution.
MAST is serving a OM Mosaic product that uses a pipeline described by Kuntz et. al. OMCat: Catalog of Serendipitous Sources Detected with the XMM-Newton Optical Monitor PASP, 120:740-758
</description>
						<source format="bibcode">2000FooBa...1Q....X</source>
            <referenceURL>http://archive.stsci.edu/xmm-om/ </referenceURL>
            <type>Archive</type>
            <contentLevel>Research</contentLevel>
            <contentLevel>Elementary Education</contentLevel>
          </content>
          <rights>public</rights>
          <rights rightsURI="http://creativecommons.org/publicdomain/zero/1.0/">This resource is in the public domain</rights>
          <capability xsi:type="sia:SimpleImageAccess" standardID="ivo://ivoa.net/std/SIA">
            <validationLevel validatedBy="ivo://archive.stsci.edu/nvoregistry">2</validationLevel>
            <interface xsi:type="vs:ParamHTTP" role="std" version="1.0">
              <accessURL use="base">http://archive.stsci.edu/siap/search.php?id=XMM-OM&amp;</accessURL>
							<mirrorURL>http://archive.eso.org/siap/search.php?id=XMM-OM&amp;</mirrorURL>
							<mirrorURL>http://mirrors.cadc.ca/siap/%23XMM-OM?</mirrorURL>
              <queryType>GET</queryType>
            </interface>
            <imageServiceType>Pointed</imageServiceType>
              <testQuery>
              <pos>
                <long>326.6</long>
                <lat>-21.3</lat>
              </pos>
              <size>
                <long>5.0</long>
                <lat>5.0</lat>
              </size>
            </testQuery>
          </capability>

          <instrument>XMM</instrument>

          <coverage>
            <footprint ivo-id="ivo://foot/print">http://foot.edu/print</footprint>
            <waveband>Optical</waveband>
            <regionOfRegard>0.00001</regionOfRegard>
          </coverage>

        </ri:Resource>
      </oai:metadata>
    </oai:record>
   </oai:ListRecords>
</oai:OAI-PMH>
""".encode("utf-8")

class _FakeResponse(object):
	"""a minimal stand-in for a request.Response.
	"""
	def __init__(self, content, headers, httpStatus):
		self.text = content
		self.headers = headers
		self.status_code = httpStatus

	def raise_for_status(self):
		if self.status_code>=400:
			raise requests.exceptions.HTTPError(self.status_code, response=self)


def _makeResponse(content, modificationDate=1.5e9, httpStatus=200):
	"""returns a function that returns response objects with
	content and modificationDate.

	modificationDate is a unix time.
	"""
	def getResponse(url, headers=None):
		headersOut = {}
		if modificationDate:
			headersOut["last-modified"] = utils.formatRFC2616Date(
				modificationDate)

		if "if-modified-since" in headers and modificationDate:
			if utils.parseRFC2616Date(
					headers["if-modified-since"])>modificationDate:
				return _FakeResponse("", headersOut, 304)

		return _FakeResponse(content, headersOut, httpStatus)
	
	return getResponse


@contextlib.contextmanager
def fakedResources(*ids):
	"""temporarily inserts some records with ivoids ids into the DB.
	"""
	with api.getWritableAdminConn() as conn:
		for index, ivoid in enumerate(ids):
			url = "http://provider.fake/rec%s.xml"%index
			conn.execute("INSERT INTO purx.sources (source_url, ivoid, access_code)"
				" VALUES(%(url)s, %(ivoid)s, 'fake')", locals())
		conn.commit()
		try:
			yield
		finally:
			conn.execute("DELETE FROM purx.sources WHERE access_code='fake'")
			conn.commit()


@contextlib.contextmanager
def fakeResponse(
		url="http://provider.fake/oai/rec.xml",
		response=_makeResponse(SAMPLE_RECORD)):
	"""temporarily makes some fake responses available to getRegistryRecord.
	"""
	enrollment.TEST_DATA[url] = response
	try:
		yield url
	finally:
		del enrollment.TEST_DATA[url]


@contextlib.contextmanager
def mailCollector():
	accum = []
	def fakeSendMail(mailText):
		accum.append(mailText)

	realSendMail = osinter.sendMail
	osinter.sendMail = fakeSendMail
	try:
		yield accum
	finally:
		osinter.sendMail = realSendMail


class _RegularDBRecord(testhelpers.TestResource):
	def make(self, deps):
		return enrollment.makeDBRecord(
			"http://provider.fake/oai/rec.xml",
			etree.XML(SAMPLE_RECORD))

_regularDBRecord = _RegularDBRecord()

class ValidDBRecordTest(testhelpers.VerboseTest):
	resources = [("rec", _regularDBRecord)]

	def testStatus(self):
		self.assertEqual(self.rec["status"], "PENDING")
	
	def testSourceURICopied(self):
		self.assertEqual(self.rec["source_url"], "http://provider.fake/oai/rec.xml")

	def testUpdateDatePlausible(self):
		self.assertTrue(
			(datetime.datetime.utcnow()-self.rec["rectimestamp"]).total_seconds()<10,
			"rectimestamp is too far in the past")

	def testUpstreamUpdate(self):
		self.assertEqual(self.rec["upstream_update"],
			datetime.datetime(2015, 7, 21, 7, 7, 39))

	def testLastModified(self):
		self.assertEqual(self.rec["modification_date"], None)

	def testIdentifierInferred(self):
		self.assertEqual(self.rec["ivoid"], "ivo://purx/std/glots")
	
	def testConfirmationKeyGenerated(self):
		self.assertNotEqual(
			self.rec["access_code"], enrollment.makeConfirmationKey())

	def testContactEmail(self):
		self.assertEqual(self.rec["contact_email"],
			"operator@ari.uni-heidelberg.de")

	def testXMLSource(self):
		self.assertTrue('xmlns:ri="http://www.ivoa.net/xml/RegistryInterface/v1.0'
			in self.rec["xml_source"])

	def testTitle(self):
		self.assertEqual(self.rec["title"], "The Global TAP Schema GloTS")


class XMLSerialisationTest(testhelpers.VerboseTest):
	def testToplevelPrefixes(self):
		ser = enrollment.serializeTree(etree.XML(SAMPLE_RECORD))
		self.assertTrue(
			b'xmlns:ri="http://www.ivoa.net/xml/RegistryInterface/v1.0"'
			in ser)
		self.assertTrue(
			b'xmlns:vstd="http://www.ivoa.net/xml/StandardsRegExt/v1.0"'
			in ser)

	def testNestedprefixes(self):
		with fakeResponse(
				"http://provider.fake/oai/siap.xml",
				_makeResponse(SIAP_RECORD)) as url:
			ser = enrollment.serializeTree(
				enrollment.getRegistryRecord(url)[0])
			self.assertTrue(
				b'xmlns:ri="http://www.ivoa.net/xml/RegistryInterface/v1.0"'
				in ser)
			self.assertTrue(
				b'xmlns:sia="http://www.ivoa.net/xml/SIA/v1.1"'
				in ser)


class BrokenDBRecordTest(testhelpers.VerboseTest):
	def testInvalidRecord(self):
		self.assertRaisesWithMsg(api.ValidationError,
			"Field url: Resource record is not valid VOResource.  Error(s): <string>:2:0:ERROR:SCHEMASV:SCHEMAV_ELEMENT_CONTENT: Element 'title': This element is not expected. Expected is one of ( shortName, identifier ).",
			enrollment.makeDBRecord, (
				"http://provider.fake/oai/rec.xml",
				etree.XML(
					oaiclient._addCanonicalNSDecls(
						REC_SOURCE.replace(
							"</title>", "</title><title>auch das</title>")))))


	def testNoMail(self):
		self.assertRaisesWithMsg(api.ValidationError,
			"Field url: No or invalid contact email in the OAI record.",
			enrollment.makeDBRecord, (
				"http://provider.fake/oai/rec.xml",
				etree.XML(
					oaiclient._addCanonicalNSDecls(
						re.sub("<email>.*?</email>", "", REC_SOURCE)))))


class IdMakingTest(testhelpers.VerboseTest):
	def testSimpleGeneration(self):
		self.assertEqual(
			enrollment.makeIdFor(
				"ivo://x-unregistered/my/resource",
				"http://provider.fake/oai/rec.xml"),
			"ivo://purx/my/resource")

	def testHostFallback(self):
		with fakedResources("ivo://purx/my/resource"):
			self.assertEqual(
				enrollment.makeIdFor(
					"ivo://x-unregistered/my/resource",
					"http://provider.fake/oai/rec.xml"),
				"ivo://purx/provider/my/resource")

	def testNumericFallback(self):
		with fakedResources(
				"ivo://purx/my/resource",
				"ivo://purx/provider/my/resource"):
			self.assertEqual(
				enrollment.makeIdFor(
					"ivo://x-unregistered/my/resource",
					"http://provider.fake/oai/rec.xml"),
				"ivo://purx/provider/my/resource/1")


class _SentMail(testhelpers.TestResource):
	resources = [("rec", _regularDBRecord)]

	def make(self, deps):
		import email
		return email.message_from_string(
			enrollment.prepareMailing(deps["rec"]))


class MailingTest(testhelpers.VerboseTest):
	resources = [('mail', _SentMail()), ("rec", _regularDBRecord)]

	def testSyntax(self):
		self.assertEqual(self.mail.defects, [])
	
	def testAddressee(self):
		self.assertEqual(self.mail.get("to"), "operator@ari.uni-heidelberg.de")
	
	def testSender(self):
		self.assertEqual(self.mail.get("from"),
			api.EqualingRE(
				'purx admin <{}>'.format(api.getConfig("maintainerAddress"))))

	def testConfirmationURLPresent(self):
		self.assertTrue("confirm/{}".format(self.rec["access_code"])
			in self.mail.get_payload())

	def testInspectionURLPresent(self):
		self.assertTrue("/purx/q/urlstatus/qp/http%3A//provider.fake/oai/rec.xml" in self.mail.get_payload())


class EnrollmentTest(testhelpers.VerboseTest):
	sourceURLs = set(["http://provider.fake/oai/rec.xml",
		"http://provider.fake/oai/siap.xml"])

	def tearDown(self):
		with api.getWritableAdminConn() as conn:
			conn.execute("DELETE FROM purx.sources WHERE"
				" source_url in %(su)s", {"su": self.sourceURLs})

	def testDoubleEnrollment(self):
		with fakeResponse() as url:
			enrollment.runEnrollmentFor(url, force=True)
			self.assertRaisesWithMsg(api.ValidationError,
				"Field url: Someone else has already submitted this record for inclusion.  If you don't find a corresponding mail in your mailbox, contact the operators.",
				enrollment.runEnrollmentFor,
				(url,))
	
	def testWithOAIEnvelope(self):
		with fakeResponse(
				"http://provider.fake/oai/siap.xml",
				_makeResponse(SIAP_RECORD)) as url:
			enrollment.runEnrollmentFor(url)
			with api.getTableConn() as conn:
				rec = list(conn.queryToDicts("SELECT * FROM purx.sources WHERE"
					" ivoid='ivo://purx/siap/xmm-om'"))[0]
				self.assertEqual(etree.XML(rec["xml_source"]).tag,
					"{http://www.ivoa.net/xml/RegistryInterface/v1.0}Resource")

	def testDeletedEnrollment(self):
		with fakeResponse() as url:
			rec = enrollment.runEnrollmentFor(url, force=True)
			rec = enrollment.runConfirmationFor(rec["access_code"])
			enrollment.StateMachine.markDeleted(rec, None)
			enrollment.updateRow(rec)
			rec = enrollment.runEnrollmentFor(url)
			self.assertEqual(rec["status"], "DELETED")
			self.assertEqual(rec["xml_source"], None)
			rec = enrollment.runConfirmationFor(rec["access_code"])
			self.assertEqual(rec["status"], "OK")
			self.assertTrue(rec["xml_source"] is not None)

	def testWithoutLastModified(self):
		with fakeResponse("http://provider.fake/oai/rec.xml",
				_makeResponse(SAMPLE_RECORD, modificationDate=None)) as url:
			rec = enrollment.runEnrollmentFor(url)
			self.assertTrue(rec["modification_date"]-time.time()<1)


class UpdatingTest(testhelpers.VerboseTest):

	harvestInterval = datetime.timedelta(seconds=3.1e5)

	def setUp(self):
		with fakeResponse() as url:
			self.row = enrollment.runEnrollmentFor(url, force=True)
			enrollment.runConfirmationFor(self.row["access_code"])
			self.row = enrollment.getRow(url)
			self.row["rectimestamp"] -= self.harvestInterval

	def tearDown(self):
		with api.getWritableAdminConn() as conn:
			conn.execute(
				"DELETE FROM purx.sources WHERE source_url=%(source_url)s",
				self.row)

	def testNormalUpdate(self):
		src = SAMPLE_RECORD.replace(b"<title>", b"<title>Updated"
			).replace(b'updated="2015-07-21T07:07:39Z"',
				utils.bytify(
					'updated="%s"'%utils.formatISODT(datetime.datetime.utcnow())))
		with fakeResponse(response=_makeResponse(src)):
			enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertTrue("<title>Updated" in newRow["xml_source"])
		self.assertTrue(newRow["upstream_update"],
			datetime.datetime(2017, 7,  21, 7, 7, 29))
		self.assertTrue(
			(datetime.datetime.utcnow()-newRow["rectimestamp"]).total_seconds()<10)

	def testEmailUpdated(self):
		src = SAMPLE_RECORD.replace(b"<email>msdemlei", b"<email>gavo"
			).replace(b'updated="2015-07-21T07:07:39Z"',
				utils.bytify(
					'updated="%s"'%utils.formatISODT(datetime.datetime.utcnow())))
		with fakeResponse(response=_makeResponse(src)):
			enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertTrue(newRow["contact_email"],
			"gavo@ari.uni-heidelberg.de")

	def testModifiedSinceSkip(self):
		origMD = self.row["modification_date"]
		with fakeResponse(response=_makeResponse(
				SAMPLE_RECORD, modificationDate=1e9)):
			enrollment.updateSource(self.row)
		self.assertEqual(origMD, self.row["modification_date"])

	def testUnchangedUpdate(self):
		with fakeResponse(response=_makeResponse(
				SAMPLE_RECORD, modificationDate=1.50001e9)):
			self.assertRaisesWithMsg(
				enrollment.NotModified,
				"@updated on record says it's the same thing",
				enrollment.updateSource,
				(self.row,))

	def testSourceGone(self):
		with fakeResponse(response=_makeResponse(
					"Not Found", httpStatus=404)) as url:
			with mailCollector() as mails:
				enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertEqual(newRow["status"], "FAIL1")
		self.assertEqual(mails, [])

	def testMailAfterAWhile(self):
		with fakeResponse(response=_makeResponse(
					"Not Found", httpStatus=404)) as url:
			with mailCollector() as mails:
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertEqual(newRow["status"], "FAIL2")
		self.assertTrue(mails[0].startswith("From: purx admin"))
		self.assertTrue(mails[0].endswith("Thanks.\n"))

	def testEventuallyDeleted(self):
		with fakeResponse(response=_makeResponse(
					"Not Found", httpStatus=404)) as url:
			with mailCollector() as mails:
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertEqual(newRow["status"], "DELETED")
		self.assertEqual(newRow["xml_source"], None)
		self.assertTrue(
			(datetime.datetime.utcnow()-newRow["rectimestamp"]).total_seconds()<10)
		self.assertTrue(mails[0].startswith("From: purx admin"))
		self.assertTrue(mails[0].endswith("Thanks.\n"))
		self.assertTrue("A while ago I complained" in mails[1])

	def testInvalidUpdateRejected(self):
		src = SAMPLE_RECORD.replace(b"<title>", b"<title>Bad</title><title>Updated"
			).replace(b'updated="2015-07-21T07:07:39"',
				utils.bytify(
					'updated="%s"'%utils.formatISODT(datetime.datetime.utcnow())))
		with mailCollector() as mails:
			with fakeResponse(response=_makeResponse(src)):
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
		self.assertEqual(len(mails), 1)
		self.assertTrue("Expected is one of ( shortName, identifier " in mails[0],
			"invalid mail wisecracking missing.")

	def testNonwellUpdateRejected(self):
		src = SAMPLE_RECORD.replace(b"<title>", b"<itle>Updated"
			).replace(b'updated="2015-07-21T07:07:39"',
				utils.bytify(
					'updated="%s"'%utils.formatISODT(datetime.datetime.utcnow())))
		with mailCollector() as mails:
			with fakeResponse(response=_makeResponse(src)):
				enrollment.updateSource(self.row)
				self.row["rectimestamp"] -= self.harvestInterval
				enrollment.updateSource(self.row)
		self.assertEqual(len(mails), 1)
		self.assertTrue("Subject: Your VO resource ivo://purx/std/glots"
			in mails[0], "nonwell mail subject missing")
		self.assertTrue("Opening and ending tag mismatch: itle line 2" in mails[0],
			"nonwell mail doesn't contain error message")
	
	def testPendingRemoved(self):
		self.row["status"] = "PENDING"
		with api.getTableConn() as conn:
			recs = list(conn.queryToDicts("SELECT ivoid FROM purx.sources"
				" WHERE source_url=%(source_url)s", self.row))
			self.assertEqual(len(recs), 1)
			enrollment.updateRow(self.row)
			enrollment.expireStalePENDING(1.5e5)
			recs = list(conn.queryToDicts("SELECT ivoid FROM purx.sources"
				" WHERE source_url=%(source_url)s", self.row))
		self.assertEqual(recs, [])

	def testRecovery(self):
		with fakeResponse(response=_makeResponse(
				"Not Found", httpStatus=404)) as url:
			enrollment.updateSource(self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertEqual(newRow["status"], "FAIL1")

		with fakeResponse(response=_makeResponse(
				SAMPLE_RECORD, modificationDate=1.50001e9)):
			self.assertRaises(enrollment.NotModified,
				enrollment.updateSource,
				self.row)

		newRow = enrollment.getRow("http://provider.fake/oai/rec.xml")
		self.assertEqual(newRow["status"], "OK")


class _SampleRecords(testhelpers.TestResource):
	
	sources = [
		("http://provider.fake/oai/rec.xml", _makeResponse(SAMPLE_RECORD)),
		("http://provider.fake/oai/siap.xml",
			_makeResponse(SIAP_RECORD.replace(b"xmm-om", b"xmm-hum"))),
		("http://provider.fake/oai/siap2.xml",
			_makeResponse(SIAP_RECORD.replace(b"xmm-om", b"xmm-hum")))]

	def make(self, deps):
		with api.getWritableAdminConn() as conn:
			conn.execute("delete from purx.sources where source_url like"
				" 'http://provider.fake%%'", {})

		with fakeResponse(*self.sources[0]) as url:
			rec = enrollment.runEnrollmentFor(url, force=True)

		with fakeResponse(*self.sources[1]) as url:
			rec = enrollment.runEnrollmentFor(url, force=True)
			enrollment.runConfirmationFor(rec["access_code"])

		with fakeResponse(*self.sources[2]) as url:
			rec = enrollment.runEnrollmentFor(url, force=True)
			rec = enrollment.runConfirmationFor(rec["access_code"])
			enrollment.StateMachine.process(rec, "forbidden")

		# I'm too lazy to reproduce the circumstances for inserting
		# a deleted record that doesn't have the original XML source
		# any more; manually does it:
		with api.getWritableAdminConn() as conn:
			conn.execute("insert into purx.sources"
				" (status, title, upstream_update, rectimestamp, "
				" source_url, ivoid)"
				" VALUES ('DELETED', 'A goner', '2018-10-10', '2018-10-12',"
				"  'http://provider.fake/dead.xml', 'ivo://purx/docless')")

	def clean(self, res):
		with api.getWritableAdminConn() as conn:
			conn.execute("DELETE FROM purx.sources WHERE"
				" source_url LIKE 'http://provider.fake%%'")

_sampleRecords = _SampleRecords()


class _OAIResponse(testhelpers.TestResource):
	# This is abstract! you need to fill in oai_prefix.

	oai_prefix = None

	resources = [('recs', _sampleRecords)]

	def make(self, deps):
		tree = oaiinter.runPMH({
					"verb": "ListRecords",
					"set": "ivo_managed",
					"metadataPrefix": self.oai_prefix},
				purxoai.Core.builders)
	
		rendered = utils.xmlrender(tree)
		return testhelpers.getXMLTree(rendered, debug=False), rendered
		

class _OAI_IVOResponse(_OAIResponse):
	oai_prefix = "ivo_vor"


class OAI_IVOResponseTest(testhelpers.VerboseTest, testhelpers.XSDTestMixin):
	resources = [("treeAndSource", _OAI_IVOResponse())]

	def testPublishedRecord(self):
		self.assertEqual(self.treeAndSource[0].xpath(
			"//Resource[identifier='ivo://purx/siap/xmm-hum']/title")[0].text,
			"X-ray Multi-Mirror (XMM) Optical Monitor images")

	def testRegularDeletedRecord(self):
		rec = self.treeAndSource[0].xpath(
			"//record[header[identifier='ivo://purx/provider/siap/xmm-hum']]")[0]
		self.assertEqual(rec.find("header").get("status"), "deleted")
		self.assertEqual(len(rec.xpath("metadata")), 0)

	def testDoclessDeletedRecord(self):
		rec = self.treeAndSource[0].xpath(
			"//record[header[identifier='ivo://purx/docless']]")[0]
		self.assertEqual(rec.find("header").get("status"), "deleted")
		self.assertEqual(len(rec.xpath("metadata")), 0)

	def testPendingRecordNotShown(self):
		self.assertEqual(self.treeAndSource[0].xpath(
			"//Resource[identifier='ivo://purx/std/glots']"),
			[])

	def testValid(self):
		self.assertValidates(self.treeAndSource[1])


class _OAI_DCResponse(_OAIResponse):
	oai_prefix = "oai_dc"


class OAI_DCResponseTest(testhelpers.VerboseTest, testhelpers.XSDTestMixin):
	resources = [("treeAndSource", _OAI_DCResponse())]

	def testPublishedRecord(self):
		self.assertEqual(self.treeAndSource[0].xpath(
			"//od:dc[identifier='ivo://purx/siap/xmm-hum']/title",
			namespaces={"od": "http://www.openarchives.org/OAI/2.0/oai_dc/"})[0].text,
			"X-ray Multi-Mirror (XMM) Optical Monitor images")

	def testRegularDeletedRecord(self):
		rec = self.treeAndSource[0].xpath(
			"//record[header[identifier='ivo://purx/provider/siap/xmm-hum']]")[0]
		self.assertEqual(rec.find("header").get("status"), "deleted")
		self.assertEqual(len(rec.xpath("metadata")), 0)

	def testDoclessDeletedRecord(self):
		rec = self.treeAndSource[0].xpath(
			"//record[header[identifier='ivo://purx/docless']]")[0]
		self.assertEqual(rec.find("header").get("status"), "deleted")
		self.assertEqual(len(rec.xpath("metadata")), 0)

	def testPendingRecordNotShown(self):
		self.assertEqual(self.treeAndSource[0].xpath(
			"//Resource[identifier='ivo://purx/std/glots']"),
			[])

	def testValid(self):
		self.assertValidates(self.treeAndSource[1])


class _OAIIdentify(testhelpers.TestResource):
	def make(self, deps):
		tree = purxoai.Core(None).runWithPMHDict({"verb": ["Identify"]})
		rendered = utils.xmlrender(tree)
		return testhelpers.getXMLTree(rendered, debug=False), rendered


class OAIIdentifyTest(testhelpers.VerboseTest, testhelpers.XSDTestMixin):
	resources = [("treeAndSource", _OAIIdentify())]

	def testValid(self):
		self.assertValidates(self.treeAndSource[1])

	def testManagedAuth(self):
		ma = self.treeAndSource[0].xpath("//managedAuthority")
		self.assertEqual(len(ma), 1)
		self.assertEqual(ma[0].text, "purx")


if __name__=="__main__":
	testhelpers.main(ValidDBRecordTest)
