feat(import): RPRO3-Importer um 6 Feature-Tabellen erweitern
becken→Gehege-Reinigung, herktier→Erwerb, abgeben/abstat→Reservierung, getback→Rücknahmen, nachfrage→Warteliste, ausz→Ausstellungen. Deterministische GUIDs, idempotenter Re-Import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,97 @@ namespace GerbilManager.Tests
|
||||
finally { try { File.Delete(dbPath); } catch { } }
|
||||
}
|
||||
|
||||
// ── Integration: Feature-Tabellen (Becken/Erwerb/Reservierung/Rücknahme/Warteliste/Ausstellung) ──
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_imports_feature_tables_and_is_idempotent()
|
||||
{
|
||||
var dbPath = BuildMiniRpro3();
|
||||
try
|
||||
{
|
||||
var data = new Rpro3Reader().ReadFromDbFile(dbPath);
|
||||
// Reader hat alle Feature-Tabellen erfasst.
|
||||
Assert.Equal(2, data.Becken.Count);
|
||||
Assert.Single(data.Acquisitions);
|
||||
Assert.Single(data.Abgeben);
|
||||
Assert.Single(data.Abstat);
|
||||
Assert.Single(data.Getback);
|
||||
Assert.Single(data.Nachfrage);
|
||||
Assert.Single(data.Ausstellungen);
|
||||
Assert.Equal(1, data.StammBecken["1"]); // Blacky → Becken 1
|
||||
|
||||
using var efConn = new SqliteConnection("DataSource=:memory:");
|
||||
efConn.Open();
|
||||
var opts = new DbContextOptionsBuilder<ApplicationContext>().UseSqlite(efConn).Options;
|
||||
using var ctx = new ApplicationContext(opts);
|
||||
ctx.Database.EnsureCreated();
|
||||
var service = new Rpro3ImportService(ctx, new ConfigurationBuilder().Build(), null);
|
||||
|
||||
var res = await service.ExecuteAsync(data, Path.GetTempPath(), photosProvided: false, photoSourcePaths: null);
|
||||
Assert.Equal(2, res.EnclosuresImported);
|
||||
Assert.Equal(1, res.AcquisitionsImported);
|
||||
Assert.Equal(2, res.ReservationsImported); // abgeben(Blacky, reserviert) + abstat(Kuke, abgegeben)
|
||||
Assert.Equal(1, res.ReturnsImported);
|
||||
Assert.Equal(1, res.WaitingListImported);
|
||||
Assert.Equal(1, res.ExhibitionsImported);
|
||||
|
||||
// Becken → Gehege; Blacky ist Becken 1 zugeordnet.
|
||||
Assert.Equal(2, await ctx.Enclosures.CountAsync());
|
||||
var kaefig = await ctx.Enclosures.FirstAsync(e => e.Name == "Käfig A");
|
||||
Assert.Equal("80x35x40", kaefig.Size);
|
||||
Assert.Equal(3, kaefig.Capacity);
|
||||
Assert.Equal(60, kaefig.CleaningCycleDays);
|
||||
var blacky = await ctx.Gerbils.FirstAsync(g => g.Name == "Blacky");
|
||||
Assert.Equal(kaefig.Id, blacky.EnclosureId);
|
||||
|
||||
// Erwerb verknüpft auf Blacky mit Preis/Datum.
|
||||
var acq = await ctx.AcquisitionRecords.SingleAsync();
|
||||
Assert.Equal(blacky.Id, acq.GerbilId);
|
||||
Assert.Equal(9.5m, acq.Price);
|
||||
Assert.NotNull(acq.Date);
|
||||
|
||||
// Reservierung: Blacky reserviert für Lisa M.
|
||||
var resv = await ctx.SaleReservations.FirstAsync(r => r.GerbilId == blacky.Id);
|
||||
Assert.Equal("reserviert", resv.Status);
|
||||
Assert.Equal("Lisa M.", resv.ContactName);
|
||||
|
||||
// Abgabe-Abschluss: Kuke → abgegeben.
|
||||
var kuke = await ctx.Gerbils.FirstAsync(g => g.Name == "Kuke");
|
||||
var kukeRes = await ctx.SaleReservations.FirstAsync(r => r.GerbilId == kuke.Id);
|
||||
Assert.Equal("abgegeben", kukeRes.Status);
|
||||
Assert.Equal(15.0m, kukeRes.Price);
|
||||
|
||||
// Rücknahme: Blacky kam zurück.
|
||||
var ret = await ctx.ReturnRecords.SingleAsync();
|
||||
Assert.Equal(blacky.Id, ret.GerbilId);
|
||||
Assert.Equal(15.0m, ret.OriginalPrice);
|
||||
Assert.Equal("Lisa M.", ret.FromContactName);
|
||||
|
||||
// Warteliste: männlicher Schimmel.
|
||||
var wl = await ctx.WaitingListEntries.SingleAsync();
|
||||
Assert.Equal("male", wl.WishGender);
|
||||
Assert.Equal("Schimmel", wl.WishColor);
|
||||
Assert.Equal("offen", wl.Status);
|
||||
|
||||
// Ausstellung: Blacky, 1. Platz, BOB.
|
||||
var exh = await ctx.ExhibitionResults.SingleAsync();
|
||||
Assert.Equal(blacky.Id, exh.GerbilId);
|
||||
Assert.Equal("Schau 2011", exh.EventName);
|
||||
Assert.Equal("1. Platz", exh.Placement);
|
||||
Assert.Equal("BOB", exh.Award);
|
||||
|
||||
// Re-Run: idempotent — keine Dubletten in den Feature-Tabellen.
|
||||
await service.ExecuteAsync(data, Path.GetTempPath(), photosProvided: false, photoSourcePaths: null);
|
||||
Assert.Equal(2, await ctx.Enclosures.CountAsync());
|
||||
Assert.Equal(1, await ctx.AcquisitionRecords.CountAsync());
|
||||
Assert.Equal(2, await ctx.SaleReservations.CountAsync()); // Blacky + Kuke
|
||||
Assert.Equal(1, await ctx.ReturnRecords.CountAsync());
|
||||
Assert.Equal(1, await ctx.WaitingListEntries.CountAsync());
|
||||
Assert.Equal(1, await ctx.ExhibitionResults.CountAsync());
|
||||
}
|
||||
finally { try { File.Delete(dbPath); } catch { } }
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
private static Rpro3Animal Ext(string rid, string name, DateOnly? dob, string farbe, string origin) =>
|
||||
@@ -157,7 +248,7 @@ namespace GerbilManager.Tests
|
||||
Exec("CREATE TABLE herk_tb (id INTEGER, _BEZ, _ANREDE, _VNAME, _NNAME, _CLAN, _STR, _ORT, _PLZ, _TELP, _TELD, _MAIL, _HTTP, _MEMO, _BEM)");
|
||||
Exec("CREATE TABLE abn_tb (id INTEGER, _BEZ, _ANREDE, _VNAME, _NNAME, _CLAN, _STR, _ORT, _PLZ, _TELP, _TELD, _MAIL, _HTTP, _MEMO, _BEM)");
|
||||
Exec("CREATE TABLE baum_tb (id, _MID, _PID)");
|
||||
Exec("CREATE TABLE stamm_tb (id INTEGER, _NAME, _SEX, _BIRTH, _HERKUNFT, _ZB, _STATUS, _FEHLER, _KASTRAT_DATE)");
|
||||
Exec("CREATE TABLE stamm_tb (id INTEGER, _NAME, _SEX, _BIRTH, _HERKUNFT, _ZB, _STATUS, _FEHLER, _KASTRAT_DATE, _BECKEN)");
|
||||
Exec("CREATE TABLE fremd_tb (id INTEGER, _NAME, _SEX, _BIRTH, _ZB, _HERK, _MID, _PID, _TODAM, _KASTRAT_DATE)");
|
||||
Exec("CREATE TABLE wurf_tb (id INTEGER, _MID, _PID, _AM, _BEZ, _BEM)");
|
||||
Exec("CREATE TABLE wurftier_tb (id, _WID, _NAME, _SEX, _BIRTH, _SID, _TODAM, _WARUM, _ABAM, _ABN, _PRICE, _ZB, _FEHLER)");
|
||||
@@ -170,6 +261,14 @@ namespace GerbilManager.Tests
|
||||
Exec("CREATE TABLE krank_tb (_TID, _DATE, _BEM, _MED)");
|
||||
Exec("CREATE TABLE diary_tb (_TID, _BEZ, _DATE, _DESC)");
|
||||
Exec("CREATE TABLE photo_tb (id, _P1, _P2, _P3)");
|
||||
// Feature-Tabellen.
|
||||
Exec("CREATE TABLE becken_tb (id INTEGER, _BEZ, _SIZE, _MENGE, _CLEANED, _CYCLUS, _NEXT)");
|
||||
Exec("CREATE TABLE herktier_tb (id INTEGER, _TID, _DATE, _PRICE, _BEM, _ART)");
|
||||
Exec("CREATE TABLE abgeben_tb (tid, _RES, _BEM, _TERMIN, _ABN, _BEZ)");
|
||||
Exec("CREATE TABLE abstat_tb (tid, _IDABN, _AM, _PRICE, _ART, _BEM)");
|
||||
Exec("CREATE TABLE getback_tb (id INTEGER, _TID, _ZAM, _ZPREIS, _AM, _PREIS, _ABN, _BEM)");
|
||||
Exec("CREATE TABLE nachfrage_tb (id INTEGER, _COLOR, _SEX, _DATE, _ABN, _CODE, _VOM, _STATUS, _ART)");
|
||||
Exec("CREATE TABLE ausz_tb (id INTEGER, _TID, _VERANSTALTUNG, _DATE, _ORT, _PLATZ, _AUSZ, _EXTRA, _JUROR, _BEM)");
|
||||
|
||||
// Kontakte
|
||||
Exec("INSERT INTO herk_tb (id,_BEZ,_CLAN,_ORT,_MAIL) VALUES (1,'eigene Zucht','','','')");
|
||||
@@ -178,19 +277,20 @@ namespace GerbilManager.Tests
|
||||
// Geschlecht als Latin-1-Bytes einfügen (BLOB), um den smart_decode-Fallback zu testen.
|
||||
var maennBytes = System.Text.Encoding.GetEncoding(1252).GetBytes("männlich");
|
||||
var weibBytes = System.Text.Encoding.UTF8.GetBytes("weiblich");
|
||||
void InsertStamm(int id, string name, byte[] sex, double birth, int herk, string zb)
|
||||
void InsertStamm(int id, string name, byte[] sex, double birth, int herk, string zb, int becken = 0)
|
||||
{
|
||||
using var c = conn.CreateCommand();
|
||||
c.CommandText = "INSERT INTO stamm_tb (id,_NAME,_SEX,_BIRTH,_HERKUNFT,_ZB,_STATUS,_FEHLER) VALUES ($id,$n,$s,$b,$h,$z,3,'')";
|
||||
c.CommandText = "INSERT INTO stamm_tb (id,_NAME,_SEX,_BIRTH,_HERKUNFT,_ZB,_STATUS,_FEHLER,_BECKEN) VALUES ($id,$n,$s,$b,$h,$z,3,'',$bk)";
|
||||
c.Parameters.AddWithValue("$id", id);
|
||||
c.Parameters.AddWithValue("$n", name);
|
||||
c.Parameters.AddWithValue("$s", sex);
|
||||
c.Parameters.AddWithValue("$b", birth);
|
||||
c.Parameters.AddWithValue("$h", herk);
|
||||
c.Parameters.AddWithValue("$z", zb);
|
||||
c.Parameters.AddWithValue("$bk", becken);
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
InsertStamm(1, "Blacky", maennBytes, 2455036.0, 61, "ZdKC-B.09.2009");
|
||||
InsertStamm(1, "Blacky", maennBytes, 2455036.0, 61, "ZdKC-B.09.2009", becken: 1);
|
||||
InsertStamm(2, "Kuke", weibBytes, 2455093.0, 61, "ZdKC-B.10.2009");
|
||||
|
||||
// Genotyp (Bracket-Notation) für Blacky.
|
||||
@@ -210,6 +310,25 @@ namespace GerbilManager.Tests
|
||||
Exec("INSERT INTO waage_tb (_TID,_GRAMM,_DATE,_BEM) VALUES ('1',55.0,2455083.0,'')");
|
||||
Exec("INSERT INTO krank_tb (_TID,_DATE,_BEM,_MED) VALUES ('1',2455077.0,'Prellung','')");
|
||||
|
||||
// Feature-Tabellen-Daten.
|
||||
// Abnehmer-Kontakt (für Reservierung/Rücknahme/Warteliste).
|
||||
Exec("INSERT INTO abn_tb (id,_BEZ,_VNAME,_NNAME) VALUES (5,'Lisa M.','Lisa','Müller')");
|
||||
// Becken 1 (Blacky wohnt dort, _BECKEN=1), Becken 2 ohne Bewohner.
|
||||
Exec("INSERT INTO becken_tb (id,_BEZ,_SIZE,_MENGE,_CLEANED,_CYCLUS,_NEXT) VALUES (1,'Käfig A','80x35x40',3,2455595.0,60,2455655.0)");
|
||||
Exec("INSERT INTO becken_tb (id,_BEZ,_SIZE,_MENGE,_CLEANED,_CYCLUS,_NEXT) VALUES (2,'Terrarium','55x48x110',32,2455638.0,90,2455728.0)");
|
||||
// Erwerb: Blacky (stamm 1) am JDN für 9.50.
|
||||
Exec("INSERT INTO herktier_tb (id,_TID,_DATE,_PRICE,_BEM) VALUES (61,'1',2455076.0,9.5,'gekauft')");
|
||||
// Reservierung: Blacky (stamm 1) reserviert für abn 5.
|
||||
Exec("INSERT INTO abgeben_tb (tid,_RES,_BEM,_TERMIN,_ABN,_BEZ) VALUES ('1',1,'',2455730.0,5,0)");
|
||||
// Abgabe-Abschluss: Kuke (stamm 2) abgegeben an abn 5 für 15.
|
||||
Exec("INSERT INTO abstat_tb (tid,_IDABN,_AM,_PRICE,_BEM) VALUES ('2',5,2455716.0,15.0,'Zog aus.')");
|
||||
// Rücknahme: Blacky kam zurück von abn 5.
|
||||
Exec("INSERT INTO getback_tb (id,_TID,_ZAM,_ZPREIS,_AM,_PREIS,_ABN,_BEM) VALUES (2,'1',2455992.0,0.0,2455798.0,15.0,5,'kam zurück')");
|
||||
// Warteliste: Wunsch nach männlichem Schimmel von abn 5.
|
||||
Exec("INSERT INTO nachfrage_tb (id,_COLOR,_SEX,_DATE,_ABN,_CODE,_VOM,_STATUS) VALUES (1,'Schimmel','männlich',0.0,5,'egal',2456172.0,'')");
|
||||
// Ausstellung: ein Ergebnis für Blacky (in echten Backups oft leer).
|
||||
Exec("INSERT INTO ausz_tb (id,_TID,_VERANSTALTUNG,_DATE,_ORT,_PLATZ,_AUSZ,_JUROR,_BEM) VALUES (1,'1','Schau 2011',2455700.0,'Halle A','1. Platz','BOB','Frau X','schön')");
|
||||
|
||||
conn.Close();
|
||||
SqliteConnection.ClearAllPools();
|
||||
return path;
|
||||
|
||||
Reference in New Issue
Block a user