Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
memoriav
Memobase 2020
services
postprocessing
Media Converter
Commits
0a0f4f12
Verified
Commit
0a0f4f12
authored
Jul 21, 2020
by
Sebastian Schüpbach
Browse files
extend and fix tests by
Signed-off-by:
Sebastian Schüpbach
<
sebastian.schuepbach@unibas.ch
>
parent
58297140
Pipeline
#11829
passed with stages
in 15 minutes and 22 seconds
Changes
5
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
src/test/resources/incoming_message_
value
.json
→
src/test/resources/incoming_message_
with_binary
.json
View file @
0a0f4f12
...
...
@@ -183,7 +183,8 @@
"lastModified"
:
"2020-06-30T10:07:26.621Z"
,
"lastModifiedBy"
:
"fedoraAdmin"
,
"filename"
:
"Nicolas_Muller_snowboard_wallpaper_1920x1080.jpg"
,
"hasMimeType"
:
"image/jpeg"
,
"hasMimeType"
:
"{{mimeType}}"
,
"eventType"
:
"{{eventType}}"
,
"describedby"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1/binary/fcr:metadata"
,
"hasMessageDigest"
:
"urn:sha1:884adf9882fd76cc1c863f527bcde9cc45d7cfc0"
,
"hasSize"
:
"244220"
...
...
src/test/resources/incoming_message_without_binary.json
0 → 100644
View file @
0a0f4f12
{
"@context"
:
{
},
"@graph"
:
[
{
"@id"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1"
,
"@type"
:
[
"ldp:RDFSource"
,
"ldp:Container"
,
"ldp:BasicContainer"
,
"fedora:Container"
,
"https://www.ica.org/standards/RiC/ontology#Instantiation"
,
"fedora:Resource"
],
"created"
:
"2020-06-30T10:06:39.203Z"
,
"createdBy"
:
"fedoraAdmin"
,
"lastModified"
:
"2020-06-30T10:07:26.563Z"
,
"lastModifiedBy"
:
"fedoraAdmin"
,
"identifiedBy"
:
[
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genidd1c6f2c6-99a9-407a-970a-1ec31a8e0292"
,
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genidada014e8-ead3-459f-a7ea-ac0aaf02b392"
],
"instantiates"
:
"https://memobase.ch/record/BAZ-MEI_77466"
,
"isDerivedFromInstantiation"
:
"https://memobase.ch/instantiation/physical/BAZ-MEI_77466-0"
,
"regulatedBy"
:
[
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genid03e8a0c8-2164-4608-93b9-14a2361b90d2"
,
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genid5abe8161-110a-429f-a6b7-9e7e10dafe0d"
],
"type"
:
"digitalObject"
},
{
"@id"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genid03e8a0c8-2164-4608-93b9-14a2361b90d2"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Rule"
,
"sameAs"
:
"http://rightsstatements.org/vocab/InC/1.0/"
,
"name"
:
"In Copyright (InC)"
,
"regulates"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1"
,
"type"
:
"usage"
},
{
"@id"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genid5abe8161-110a-429f-a6b7-9e7e10dafe0d"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Rule"
,
"name"
:
"public"
,
"regulates"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1"
,
"type"
:
"access"
},
{
"@id"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genidada014e8-ead3-459f-a7ea-ac0aaf02b392"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Identifier"
,
"identifier"
:
"MEI_77466"
,
"type"
:
"original"
},
{
"@id"
:
"https://memobase.ch/instantiation/digital/BAZ-MEI_77466-1#genidd1c6f2c6-99a9-407a-970a-1ec31a8e0292"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Identifier"
,
"identifier"
:
"http://127.0.0.1:8080/fcrepo/rest/instantiation/digital/BAZ-MEI_77466-1"
,
"type"
:
"main"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466"
,
"@type"
:
[
"fedora:Resource"
,
"https://www.ica.org/standards/RiC/ontology#Record"
,
"ldp:BasicContainer"
,
"ldp:Container"
,
"fedora:Container"
,
"ldp:RDFSource"
],
"fedora:created"
:
{
"@type"
:
"http://www.w3.org/2001/XMLSchema#dateTime"
,
"@value"
:
"2020-06-30T09:45:42.286Z"
},
"createdBy"
:
"fedoraAdmin"
,
"lastModified"
:
"2020-06-30T09:45:42.286Z"
,
"lastModifiedBy"
:
"fedoraAdmin"
,
"created"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidc54a285d-a55d-43b7-9b4e-a5bb59ce0387"
,
"relation"
:
"Bezug Findmittel: Auftragsregister Bd. 9; Bildverzeichnis Bd. 13"
,
"P60451"
:
"Memoriav"
,
"P60556"
:
"https://memobase.ch/record/BAZ-MEI_77466#genid5e41f3a9-4392-49b0-b10e-464867bf9d45"
,
"descriptiveNote"
:
{
"@language"
:
"de"
,
"@value"
:
"Garderobe im Eingangsbereich mit grossem Spiegel."
},
"hasTitle"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidd775a4d4-bc4c-49f0-8da7-1cb08ef98edb"
,
"heldBy"
:
"https://memobase.ch/institution/BAZ"
,
"identifiedBy"
:
[
"https://memobase.ch/record/BAZ-MEI_77466#genida0b30045-473f-4fb4-8ed0-c41543011eb1"
,
"https://memobase.ch/record/BAZ-MEI_77466#genidbcefc1dd-8e99-4026-8ded-38b5f0814fbd"
],
"isPartOf"
:
"https://memobase.ch/recordSet/BAZ-B_MEI"
,
"recordResourceOrInstantiationIsSourceOfCreationRelation"
:
[
"https://memobase.ch/record/BAZ-MEI_77466#genid0524f985-c1d2-4a86-a239-6cdadca53632"
,
"https://memobase.ch/record/BAZ-MEI_77466#genide6bd0c30-d198-4437-97e6-ce278946e276"
],
"regulatedBy"
:
"https://memobase.ch/record/BAZ-MEI_77466#genid98f1658d-4934-4c6b-a991-c81e73affa5e"
,
"title"
:
{
"@language"
:
"de"
,
"@value"
:
"Eingangsbereich Landhaus bei Windisch, erbaut von Gisbert Meyer, Architekt"
},
"type"
:
"Foto"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genid0524f985-c1d2-4a86-a239-6cdadca53632"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#CreationRelation"
,
"creationRelationHasSource"
:
"https://memobase.ch/record/BAZ-MEI_77466"
,
"creationRelationHasTarget"
:
"https://memobase.ch/record/BAZ-MEI_77466#genida8ce0c15-3b0f-46c2-b2a3-64e605bc03d2"
,
"type"
:
"Auftraggeber"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genid5e41f3a9-4392-49b0-b10e-464867bf9d45"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Place"
,
"name"
:
{
"@language"
:
"de"
,
"@value"
:
"Luzern"
}
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genid98f1658d-4934-4c6b-a991-c81e73affa5e"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Rule"
,
"name"
:
"BAZ"
,
"regulates"
:
"https://memobase.ch/record/BAZ-MEI_77466"
,
"type"
:
"holder"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genida0b30045-473f-4fb4-8ed0-c41543011eb1"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Identifier"
,
"identifier"
:
"MEI_77466"
,
"type"
:
"callNumber"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genida8ce0c15-3b0f-46c2-b2a3-64e605bc03d2"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Agent"
,
"name"
:
{
"@language"
:
"de"
,
"@value"
:
"Meyer, Gisbert"
}
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidbcefc1dd-8e99-4026-8ded-38b5f0814fbd"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Identifier"
,
"identifier"
:
"http://127.0.0.1:8080/fcrepo/rest/record/BAZ-MEI_77466"
,
"type"
:
"main"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidc54a285d-a55d-43b7-9b4e-a5bb59ce0387"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#DateSet"
,
"expressedDate"
:
"19420128"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidca164cd4-d561-4190-a63b-41fe3614de2c"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#CorporateBody"
,
"name"
:
{
"@language"
:
"de"
,
"@value"
:
"Atelier Meiner"
}
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidd775a4d4-bc4c-49f0-8da7-1cb08ef98edb"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#Title"
,
"title"
:
{
"@language"
:
"de"
,
"@value"
:
"Eingangsbereich Landhaus bei Windisch, erbaut von Gisbert Meyer, Architekt"
},
"type"
:
"main"
},
{
"@id"
:
"https://memobase.ch/record/BAZ-MEI_77466#genide6bd0c30-d198-4437-97e6-ce278946e276"
,
"@type"
:
"https://www.ica.org/standards/RiC/ontology#CreationRelation"
,
"creationRelationHasSource"
:
"https://memobase.ch/record/BAZ-MEI_77466"
,
"creationRelationHasTarget"
:
"https://memobase.ch/record/BAZ-MEI_77466#genidca164cd4-d561-4190-a63b-41fe3614de2c"
,
"type"
:
"Fotograf"
}
]
}
src/test/scala/ch/memobase/BinaryResourceMetadataTest.scala
View file @
0a0f4f12
...
...
@@ -28,8 +28,10 @@ class BinaryResourceMetadataTest extends AnyFunSuite {
val
externalBaseUrl
=
"https://memobase.ch"
private
def
loadMessage
:
String
=
{
val
file
=
Source
.
fromFile
(
"src/test/resources/incoming_message_
value
.json"
)
val
file
=
Source
.
fromFile
(
"src/test/resources/incoming_message_
with_binary
.json"
)
val
result
=
file
.
mkString
.
replaceAll
(
raw
"\{\{eventType\}\}"
,
"Create"
)
.
replaceAll
(
raw
"\{\{mimeType\}\}"
,
"image/jpeg"
)
file
.
close
()
result
}
...
...
@@ -38,10 +40,4 @@ class BinaryResourceMetadataTest extends AnyFunSuite {
val
km
=
BinaryResourceMetadata
.
build
(
loadMessage
,
externalBaseUrl
)
assert
(
km
.
get
.
id
==
"BAZ-MEI_77466-1"
)
}
/*test("a json message non containing the binary object type should not be identified as binary") {
val km = KafkaMessage(loadMessage, externalBaseUrl)
assert(!km.isBinaryObject)
}*/
}
src/test/scala/ch/memobase/DisseminationCopyHandlerTest.scala
View file @
0a0f4f12
...
...
@@ -18,15 +18,15 @@
package
ch.memobase
import
java.io.
{
ByteArrayOutputStream
,
FileInputStream
}
import
java.io.
{
ByteArrayOutputStream
,
File
,
FileInputStream
}
import
java.nio.file.
{
Files
,
Path
,
Paths
}
import
org.scalatest.Assertion
import
org.scalatest.funsuite.AnyFunSuite
import
org.scalatest.
{
Assertion
,
BeforeAndAfter
}
import
scala.util.Try
class
DisseminationCopyHandlerTest
extends
AnyFunSuite
{
class
DisseminationCopyHandlerTest
extends
AnyFunSuite
with
BeforeAndAfter
{
private
def
fixture
=
{
new
{
...
...
@@ -35,6 +35,16 @@ class DisseminationCopyHandlerTest extends AnyFunSuite {
}
}
after
{
val
paths
=
List
(
new
File
(
"src/test/resources/test.jp2"
).
toPath
,
new
File
(
"src/test/resources/test.mp4"
).
toPath
,
)
for
(
p
<-
paths
)
{
Files
.
deleteIfExists
(
p
)
}
}
private
def
testCopy
(
pathToTmpDir
:
String
,
sourceFileName
:
String
,
destFileName
:
String
,
copyFun
:
(
ByteArrayOutputStream
,
String
)
=>
Try
[
Path
])
:
Assertion
=
{
val
file
=
Paths
.
get
(
pathToTmpDir
,
sourceFileName
).
toFile
val
data
=
new
ByteArrayOutputStream
(
file
.
length
().
toInt
)
...
...
src/test/scala/ch/memobase/RecordProcessorTest.scala
View file @
0a0f4f12
...
...
@@ -18,101 +18,98 @@
package
ch.memobase
import
org.scalatest.funsuite.AnyFunSuite
class
RecordProcessorTest
extends
AnyFunSuite
{
private
val
externalUrl
=
"https://memobase.ch"
private
def
generateJsonString
(
objectId
:
String
,
eventType
:
String
,
isBinary
:
Boolean
)
:
String
=
{
s
"""{
| "eventTimestamp": "20200602T083300+02:00",
| "eventId": "urn:uuid:550e8400-e2df-2314-a7ef-432432943201",
| "eventType": "$eventType",
| "objectPath": "http://localhost:8080/rest/test1",
| "objectId": "$objectId",
| "objectTypes": ["http://www.w3.org/ns/ldp#Container", "http://fedora.info/definitions/v4/repository#Resource", "http://fedora.info/definitions/v4/repository#Container"${if (isBinary) ", \"https://binaryObject\"" else ""}]
|}"""
.
stripMargin
}
/*
test("an object of mimeType audio/mpeg and eventType Delete should trigger deleteAudio") {
val mockFileHandler = new MockFileHandler(false)
val mockApiHandler = new MockApiHandler(Mp3File)
val rP = new RecordProcessor(mockFileHandler, mockApiHandler, externalUrl)
val recVal = generateJsonString("test1", "Delete", isBinary = true)
val cR = new ConsumerRecord[String, String]("_void", 1, 0, "1", recVal)
val res = rP.process(cR)
assert(ProcessSuccess("test1", Mp3File, "Delete") == res)
}
import
java.io.ByteArrayOutputStream
test("an object of mimeType image/jpg and eventType Create should trigger copyImage") {
val mockFileHandler = new MockFileHandler(false)
val mockApiHandler = new MockApiHandler(JpegFile)
val rP = new RecordProcessor(mockFileHandler, mockApiHandler, externalUrl)
val recVal = generateJsonString("test2", "Create", isBinary = true)
val cR = new ConsumerRecord[String, String]("_void", 1, 0, "1", recVal)
val res = rP.process(cR)
assert(ProcessSuccess("test2", JpegFile, "Create") == res)
}
import
ch.memobase.models._
import
org.apache.kafka.clients.consumer.ConsumerRecord
import
org.scalamock.scalatest.MockFactory
import
org.scalatest.funsuite.AnyFunSuite
test("a message which does not refer to a binary file should be ignored") {
val mockFileHandler = new MockFileHandler(false)
val mockApiHandler = new MockApiHandler(OtherFile(List("application/json")))
val rP = new RecordProcessor(mockFileHandler, mockApiHandler, externalUrl)
val recVal = generateJsonString("test3", "Create", isBinary = false)
val cR = new ConsumerRecord[String, String]("_void", 1, 0, "1", recVal)
val res = rP.process(cR)
assert(ProcessIgnore("test3", "is no binary object") == res)
import
scala.io.Source
import
scala.util.Success
class
RecordProcessorTest
extends
AnyFunSuite
with
MockFactory
{
def
createIncomingMessage
(
eventType
:
Event
,
mimeType
:
MediaFileType
,
hasBinaryResource
:
Boolean
=
true
)
:
(
String
,
ByteArrayOutputStream
)
=
{
if
(
hasBinaryResource
)
{
val
eT
=
eventType
match
{
case
Create
=>
"Create"
case
Delete
=>
"Delete"
case
Update
=>
"Update"
}
val
mT
=
mimeType
match
{
case
JpegFile
=>
"image/jpeg"
case
Mp3File
=>
"audio/mp3"
case
VideoMpeg4File
=>
"video/mpeg"
}
replaceTokensInIncomingMessage
(
eT
,
mT
)
}
else
{
val
is
=
Source
.
fromFile
(
"src/test/resources/incoming_message_without_binary.json"
)
val
incomingMessage
=
is
.
mkString
val
baos
=
copyIncomingMessage
(
incomingMessage
)
is
.
close
(
incomingMessage
,
baos
)
}
}
test("an API connection error should return a Failure") {
val mockFileHandler = new MockFileHandler(false)
val mockApiHandler = new MockApiHandler(OtherFile(List("image/jpg")), true)
val rP = new RecordProcessor(mockFileHandler, mockApiHandler, externalUrl)
val recVal = generateJsonString("test4", "Create", isBinary = true)
val cR = new ConsumerRecord[String, String]("_void", 1, 0, "1", recVal)
val res = rP.process(cR)
assert(res.asInstanceOf[ProcessFailure].ex.isInstanceOf[IOException])
} */
}
/*
sealed class MockFileHandler(fail: Boolean) extends FileHandler {
private val p = Paths.get("/bawef")
override def copyAudio(data: ByteArrayOutputStream, destId: String): Try[Path] =
if (fail) Failure(new Exception("should fail")) else Success(p)
override def copyImage(data: ByteArrayOutputStream, destId: String): Try[Path] =
if (fail) Failure(new Exception("should fail")) else Success(p)
private
def
replaceTokensInIncomingMessage
(
eventType
:
String
,
mimeType
:
String
)
=
{
val
is
=
Source
.
fromFile
(
"src/test/resources/incoming_message_with_binary.json"
)
val
incomingMessage
=
is
.
mkString
.
replaceAll
(
raw
"\{\{mimeType\}\}"
,
mimeType
)
.
replaceAll
(
raw
"\{\{eventType\}\}"
,
eventType
)
val
baos
=
copyIncomingMessage
(
incomingMessage
)
is
.
close
(
incomingMessage
,
baos
)
}
override def copyVideo(data: ByteArrayOutputStream, destId: String): Try[Path] =
if (fail) Failure(new Exception("should fail")) else Success(p)
private
def
copyIncomingMessage
(
incomingMessage
:
String
)
:
ByteArrayOutputStream
=
{
val
incomingMessageAsBytes
=
incomingMessage
.
getBytes
()
val
baos
=
new
ByteArrayOutputStream
()
baos
.
write
(
incomingMessageAsBytes
,
0
,
incomingMessageAsBytes
.
length
)
baos
}
override def deleteAudio(destId: String): Try[Boolean] =
if (fail) Failure(new Exception("should fail")) else Success(true)
private
val
fixture
=
new
{
val
externalBaseUrl
=
"https://memobase.ch"
val
resourceId
=
"BAZ-MEI_77466-1"
val
pathToResource
=
s
"$externalBaseUrl/instantiation/digital/$resourceId/binary"
val
mockDisseminationCopyHandler
:
DisseminationCopyHandler
=
mock
[
DisseminationCopyHandler
]
val
mockFedoraClientWrapper
:
FedoraClientWrapper
=
mock
[
FedoraClientWrapper
]
}
override def deleteImage(destId: String): Try[Boolean] =
if (fail) Failure(new Exception("should fail")) else Success(true)
test
(
"an object of mimeType image/jpg and eventType Create should trigger copyImage"
)
{
val
f
=
fixture
val
mockDCH
=
f
.
mockDisseminationCopyHandler
val
mockFCW
=
f
.
mockFedoraClientWrapper
val
(
incomingMessage
,
baos
)
=
createIncomingMessage
(
Create
,
JpegFile
)
(
mockFCW
.
fetchBinaryResource
_
).
expects
(
f
.
pathToResource
).
returning
(
Success
(
FileWithMetadata
(
baos
,
JpegFile
)))
(
mockDCH
.
createImageCopy
_
).
expects
(
baos
,
f
.
resourceId
)
val
rP
=
new
RecordProcessor
(
mockDCH
,
mockFCW
,
f
.
externalBaseUrl
)
val
cR
=
new
ConsumerRecord
[
String
,
String
](
"_void"
,
1
,
0
,
"1"
,
incomingMessage
)
rP
.
process
(
cR
)
}
override def deleteVideo(destId: String): Try[Boolean] =
if (fail) Failure(new Exception("should fail")) else Success(true)
}
test
(
"an object of mimeType audio/mpeg and eventType Delete should trigger deleteAudio"
)
{
val
f
=
fixture
val
mockDCH
=
f
.
mockDisseminationCopyHandler
val
mockFCW
=
f
.
mockFedoraClientWrapper
val
(
incomingMessage
,
baos
)
=
createIncomingMessage
(
Delete
,
Mp3File
)
(
mockFCW
.
fetchBinaryResource
_
).
expects
(
f
.
pathToResource
).
returning
(
Success
(
FileWithMetadata
(
baos
,
Mp3File
)))
(
mockDCH
.
deleteAudioCopy
_
).
expects
(
f
.
resourceId
)
val
rP
=
new
RecordProcessor
(
mockDCH
,
mockFCW
,
f
.
externalBaseUrl
)
val
cR
=
new
ConsumerRecord
[
String
,
String
](
"_void"
,
1
,
0
,
"1"
,
incomingMessage
)
rP
.
process
(
cR
)
}
sealed class MockApiHandler(fileType: MediaFileType, fail: Boolean = false) extends ApiHandler {
override def fetchBinaryResource(uri: String): Try[FileWithMetadata] = {
if (fail) {
Failure(new IOException("can't connect to API!"))
} else {
val bytes = Array[Byte](10)
val os = new ByteArrayOutputStream(bytes.length)
os.write(bytes, 0, bytes.length)
Success(FileWithMetadata(os, fileType))
}
test
(
"a message which does not refer to a binary file should be ignored"
)
{
val
f
=
fixture
val
mockDCH
=
f
.
mockDisseminationCopyHandler
val
mockFCW
=
f
.
mockFedoraClientWrapper
val
(
incomingMessage
,
_
)
=
createIncomingMessage
(
Delete
,
Mp3File
,
hasBinaryResource
=
false
)
(
mockFCW
.
fetchBinaryResource
_
).
expects
(*).
never
()
val
rP
=
new
RecordProcessor
(
mockDCH
,
mockFCW
,
f
.
externalBaseUrl
)
val
cR
=
new
ConsumerRecord
[
String
,
String
](
"_void"
,
1
,
0
,
"1"
,
incomingMessage
)
rP
.
process
(
cR
)
}
}
*/
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment