Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package eu.pretix.libpretixsync.models

import java.time.OffsetDateTime

data class ReusableMedium(
val id: Long,
val serverId: Long?,
val active: Boolean,
val customerId: Long?,
val expires: String?,
val expires: OffsetDateTime?,
val identifier: String?,
val linkedGiftCardId: Long?,
val type: String?,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package eu.pretix.libpretixsync.models.db

import eu.pretix.libpretixsync.sqldelight.ReusableMedium
import eu.pretix.libpretixsync.sqldelight.SafeOffsetDateTimeMapper
import org.json.JSONObject
import eu.pretix.libpretixsync.models.ReusableMedium as ReusableMediumModel

fun ReusableMedium.toModel(): ReusableMediumModel {
val json = JSONObject(this.json_data!!)

return ReusableMediumModel(
id = this.id,
serverId = this.server_id!!,
active = this.active,
customerId = this.customer_id,
expires = this.expires,
expires = SafeOffsetDateTimeMapper.decode(json, "expires"),
identifier = this.identifier,
linkedGiftCardId = this.linked_giftcard_id,
type = this.type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import app.cash.sqldelight.db.QueryResult
import eu.pretix.libpretixsync.api.ApiException
import eu.pretix.libpretixsync.api.PretixApi
import eu.pretix.libpretixsync.api.ResourceNotModified
import eu.pretix.libpretixsync.sqldelight.Migrations
import eu.pretix.libpretixsync.sqldelight.ResourceSyncStatus
import eu.pretix.libpretixsync.sqldelight.ReusableMedium
import eu.pretix.libpretixsync.sqldelight.SyncDatabase
import eu.pretix.libpretixsync.sync.SyncManager.ProgressFeedback
import eu.pretix.libpretixsync.utils.JSONUtils
import org.joda.time.format.ISODateTimeFormat
import org.json.JSONException
import org.json.JSONObject
import java.io.UnsupportedEncodingException
Expand Down Expand Up @@ -62,11 +65,17 @@ class ReusableMediaSyncAdapter(
}

override fun insert(jsonobj: JSONObject) {
val expires = if (!jsonobj.isNull("expires")) {
ISODateTimeFormat.dateTimeParser().parseDateTime(jsonobj.getString("expires")).toDate()
} else {
null
}

val rmId = db.reusableMediumQueries.transactionWithResult {
db.reusableMediumQueries.insert(
active = jsonobj.getBoolean("active"),
customer_id = jsonobj.optLong("customer"),
expires = jsonobj.optString("expires"),
expires = expires,
identifier = jsonobj.getString("identifier"),
json_data = jsonobj.toString(),
linked_giftcard_id = jsonobj.optLong("linked_giftcard"),
Expand All @@ -88,10 +97,16 @@ class ReusableMediaSyncAdapter(
}
.toSet()

val expires = if (!jsonobj.isNull("expires")) {
ISODateTimeFormat.dateTimeParser().parseDateTime(jsonobj.getString("expires")).toDate()
} else {
null
}

db.reusableMediumQueries.updateFromJson(
active = jsonobj.getBoolean("active"),
customer_id = jsonobj.optLong("customer"),
expires = jsonobj.optString("expires"),
expires = expires,
identifier = jsonobj.getString("identifier"),
json_data = jsonobj.toString(),
linked_giftcard_id = jsonobj.optLong("linked_giftcard"),
Expand All @@ -117,9 +132,9 @@ class ReusableMediaSyncAdapter(
}

val newIds = if (orderpositionids.isNotEmpty()) {
db.orderPositionQueries.selectByReusableMediumId(
reusablemedium_id = rmId,
).executeAsList().map { it.id }.toSet()
orderpositionids.mapNotNull {
db.orderPositionQueries.selectByServerId(it).executeAsOneOrNull()?.id
}.toSet()
} else {
emptySet()
}
Expand Down Expand Up @@ -293,4 +308,21 @@ class ReusableMediaSyncAdapter(
return d
}

@Throws(JSONException::class)
fun standaloneRefreshFromJSON(data: JSONObject) {
val known = db.reusableMediumQueries.selectByServerId(data.getLong("id")).executeAsOneOrNull()

// Store object
data.put("__libpretixsync_dbversion", Migrations.CURRENT_VERSION)
data.put("__libpretixsync_syncCycleId", syncCycleId)
if (known == null) {
insert(data)
} else {
val old = JSONObject(known.json_data!!)
if (!JSONUtils.similar(data, old)) {
update(known, data)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ FROM ReusableMedium
WHERE server_id IN ?;

selectForCheck:
SELECT ReusableMedium.*
SELECT DISTINCT ReusableMedium.*
FROM ReusableMedium
LEFT JOIN ReusableMedium_OrderPosition ON ReusableMedium_OrderPosition.OrderPositionId = ReusableMedium.id
LEFT JOIN ReusableMedium_OrderPosition ON ReusableMedium_OrderPosition.ReusableMediumId = ReusableMedium.id
LEFT JOIN OrderPosition ON ReusableMedium_OrderPosition.OrderPositionId = OrderPosition.id
LEFT JOIN orders ON OrderPosition.order_ref = orders.id
WHERE ReusableMedium.identifier = :identifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ CREATE TABLE ReusableMedium (
id serial AS Long PRIMARY KEY NOT NULL,
active boolean NOT NULL,
customer_id bigint,
expires character varying(255),
expires DATE AS Date,
identifier character varying(255),
json_data text,
linked_giftcard_id bigint,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import java.util.Date;
import kotlin.Boolean;

CREATE TABLE ReusableMedium (
id INTEGER PRIMARY KEY AUTOINCREMENT,
active INTEGER AS Boolean NOT NULL,
customer_id INTEGER,
expires TEXT,
expires TEXT AS Date,
identifier TEXT,
json_data TEXT,
linked_giftcard_id INTEGER,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package eu.pretix.libpretixsync.check

import eu.pretix.libpretixsync.db.BaseDatabaseTest
import eu.pretix.libpretixsync.sync.CheckInListSyncAdapter
import eu.pretix.libpretixsync.sync.EventSyncAdapter
import eu.pretix.libpretixsync.sync.ItemSyncAdapter
import eu.pretix.libpretixsync.sync.OrderSyncAdapter
import eu.pretix.libpretixsync.sync.ReusableMediaSyncAdapter
import eu.pretix.pretixscan.scanproxy.tests.test.FakeConfigStore
import eu.pretix.pretixscan.scanproxy.tests.test.FakeFileStorage
import eu.pretix.pretixscan.scanproxy.tests.test.FakePretixApi
import eu.pretix.pretixscan.scanproxy.tests.test.jsonResource
import org.joda.time.format.ISODateTimeFormat
import org.junit.Before
import org.junit.Test

import org.junit.Assert.assertEquals

class AsyncCheckProviderReusableMediumTest : BaseDatabaseTest() {
private var configStore: FakeConfigStore? = null
private var fakeApi: FakePretixApi? = null
private var p: AsyncCheckProvider? = null

@Before
fun setUpFakes() {
configStore = FakeConfigStore("mtrmt", "event1")
fakeApi = FakePretixApi("mtrmt")
p = AsyncCheckProvider(configStore!!, db)

EventSyncAdapter(db, "event1", "event1", fakeApi!!, "", null).standaloneRefreshFromJSON(jsonResource("events/rmevent1.json"))
EventSyncAdapter(db, "event2", "event2", fakeApi!!, "", null).standaloneRefreshFromJSON(jsonResource("events/rmevent2.json"))
ItemSyncAdapter(db, FakeFileStorage(), "event1", fakeApi!!, "", null).standaloneRefreshFromJSON(jsonResource("items/rmevent1-item1.json"))
ItemSyncAdapter(db, FakeFileStorage(), "event2", fakeApi!!, "", null).standaloneRefreshFromJSON(jsonResource("items/rmevent2-item1.json"))
CheckInListSyncAdapter(db, FakeFileStorage(), "event1", fakeApi!!, "", null, 0).standaloneRefreshFromJSON(
jsonResource("checkinlists/rmevent1-list1.json")
)
CheckInListSyncAdapter(db, FakeFileStorage(), "event2", fakeApi!!, "", null, 0).standaloneRefreshFromJSON(
jsonResource("checkinlists/rmevent2-list1.json")
)

val osa = OrderSyncAdapter(db, FakeFileStorage(), "event1", 0, true, false, fakeApi!!, "", null)
osa.standaloneRefreshFromJSON(jsonResource("orders/rmevent1-order1.json"))
val osa2 = OrderSyncAdapter(db, FakeFileStorage(), "event2", 0, true, false, fakeApi!!, "", null)
osa2.standaloneRefreshFromJSON(jsonResource("orders/rmevent2-order1.json"))

val rmsa = ReusableMediaSyncAdapter(db, FakeFileStorage(), fakeApi!!, "", null)
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium1.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium2.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium3.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium4.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium5.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium6.json"))
rmsa.standaloneRefreshFromJSON(jsonResource("reusablemedia/mtrmt-medium7.json"))
}

@Test
fun testMediumNotActive() {
val r = p!!.check(mapOf("event1" to 35L), "5555")
assertEquals(TicketCheckProvider.CheckResult.Type.INVALID, r.type)
}

@Test
fun testMediumExpired() {
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-01-01T00:00:01.000Z"))
val r = p!!.check(mapOf("event1" to 35L), "6666")
assertEquals(TicketCheckProvider.CheckResult.Type.CANCELED, r.type)
}

@Test
fun testTwoTicketsTimesOverlappingSameEvent() {
val r = p!!.check(mapOf("event1" to 35L), "1111")
assertEquals(TicketCheckProvider.CheckResult.Type.AMBIGUOUS, r.type)
}

@Test
fun testTwoTicketsTimesOverlappingDifferentEvents() {
val r = p!!.check(mapOf("event1" to 35L), "2222")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)
assertEquals("Regular ticket", r.ticket)
assertEquals("W0JKM", r.orderCode)
assertEquals(1L, r.positionId)
}

@Test
fun testTwoTicketsTimesNonOverlappingSameEvent() {
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-01-01T00:00:01.000Z"))
var r = p!!.check(mapOf("event1" to 35L), "3333")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)
assertEquals("W0JKM", r.orderCode)
assertEquals(1L, r.positionId)

p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2027-01-01T00:00:01.000Z"))
r = p!!.check(mapOf("event1" to 35L), "3333")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)
assertEquals("W0JKM", r.orderCode)
assertEquals(3L, r.positionId)
}

@Test
fun testTwoTicketsTimesNonOverlappingDifferentEvents() {
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-01-05T00:00:01.000Z"))
var r = p!!.check(mapOf("event1" to 35L), "4444")
assertEquals(TicketCheckProvider.CheckResult.Type.INVALID_TIME, r.type)

r = p!!.check(mapOf("event2" to 36L), "4444")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)

p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2027-01-05T00:00:01.000Z"))
r = p!!.check(mapOf("event1" to 35L), "4444")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)

r = p!!.check(mapOf("event2" to 36L), "4444")
assertEquals(TicketCheckProvider.CheckResult.Type.INVALID_TIME, r.type)
}

@Test
fun testTwoTicketsTimesNonOverlappingSpaceBetweenSameEvent() {
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-01-01T00:00:01.000Z"))
var r = p!!.check(mapOf("event1" to 35L), "7777")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)
assertEquals("W0JKM-6", r.orderCodeAndPositionId())

p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-09-01T00:00:01.000Z"))
r = p!!.check(mapOf("event1" to 35L), "7777")
assertEquals(TicketCheckProvider.CheckResult.Type.VALID, r.type)
assertEquals("W0JKM-7", r.orderCodeAndPositionId())

// use the candidate that will "work next"
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2026-08-01T00:00:01.000Z"))
r = p!!.check(mapOf("event1" to 35L), "7777")
assertEquals(TicketCheckProvider.CheckResult.Type.INVALID_TIME, r.type)
assertEquals("W0JKM-7", r.orderCodeAndPositionId())

// no candidate in the future, use closest from the past
p!!.setNow(ISODateTimeFormat.dateTime().parseDateTime("2027-01-01T00:00:01.000Z"))
r = p!!.check(mapOf("event1" to 35L), "7777")
assertEquals(TicketCheckProvider.CheckResult.Type.INVALID_TIME, r.type)
assertEquals("W0JKM-7", r.orderCodeAndPositionId())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import eu.pretix.libpretixsync.sqldelight.Receipt;
import eu.pretix.libpretixsync.sqldelight.ReceiptLine;
import eu.pretix.libpretixsync.sqldelight.ReceiptPayment;
import eu.pretix.libpretixsync.sqldelight.ReusableMedium;
import eu.pretix.libpretixsync.sqldelight.SubEvent;
import eu.pretix.libpretixsync.sqldelight.SyncDatabase;
import org.junit.After;
Expand Down Expand Up @@ -98,6 +99,9 @@ public void setUpDb() throws NoSuchAlgorithmException {
new ReceiptPayment.Adapter(
bigDecimalAdapter
),
new ReusableMedium.Adapter(
dateAdapter
),
new SubEvent.Adapter(
dateAdapter,
dateAdapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import eu.pretix.libpretixsync.config.ConfigStore
import eu.pretix.libpretixsync.api.PretixApi
import org.json.JSONObject

class FakeConfigStore : ConfigStore {
class FakeConfigStore(var organizer_slug: String = "demo", var event_slug: String = "demo") : ConfigStore {
private var last_download: Long = 0
private var last_sync: Long = 0
private var last_cleanup: Long = 0
Expand Down Expand Up @@ -70,11 +70,11 @@ class FakeConfigStore : ConfigStore {
}

override fun getOrganizerSlug(): String {
return "demo"
return organizer_slug
}

val eventSlug: String
get() = "demo"
get() = event_slug
val subEventId: Long?
get() = null

Expand Down Expand Up @@ -136,7 +136,7 @@ class FakeConfigStore : ConfigStore {
}

override fun getSynchronizedEvents(): List<String> {
return listOf("demo")
return listOf(event_slug)
}

override fun getSelectedSubeventForEvent(event: String): Long? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.json.JSONException
import org.json.JSONObject
import java.io.File

class FakePretixApi : PretixApi("http://1.1.1.1/", "a", "demo", 1, DefaultHttpClientFactory()) {
class FakePretixApi(var orgaSlug: String = "demo") : PretixApi("http://1.1.1.1/", "a", orgaSlug, 1, DefaultHttpClientFactory()) {
val redeemResponses: MutableList<(() -> ApiResponse)> = ArrayList()
val statusResponses: MutableList<(() -> ApiResponse)> = ArrayList()
val searchResponses: MutableList<(() -> ApiResponse)> = ArrayList()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": 35,
"name": "Default",
"all_products": true,
"limit_products": [],
"subevent": null,
"checkin_count": 0,
"position_count": 8,
"include_pending": false,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"rules": {},
"exit_all_at": null,
"addon_match": false,
"ignore_in_statistics": false,
"consider_tickets_used": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": 36,
"name": "Default",
"all_products": true,
"limit_products": [],
"subevent": null,
"checkin_count": 0,
"position_count": 8,
"include_pending": false,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"rules": {},
"exit_all_at": null,
"addon_match": false,
"ignore_in_statistics": false,
"consider_tickets_used": true
}
Loading
Loading