Ein Knopf, der nur wieder rot zurueckkommt, und eine Zeile, die niemand einlud
Zwei Reste aus dem letzten Fix, beide dieselbe Klasse: die Zeile behauptet etwas ueber den Gast, das dort nicht gilt. Der Wiederholen-Knopf an einer entzogenen, nie gespiegelten Zeile lief auf user:disable gegen einen Benutzer, den es nie gab — scheitert, dieselbe rote Zeile, beliebig oft. retry() traegt jetzt denselben nc_synced_at-Vorbehalt wie queueSync(), mit invite als Ausnahme, denn genau dafuer ist der Knopf da. Und gezeichnet wird er nur noch, wo der Server ihn auch annimmt. Nach einer Wiederaufnahme stand nc_state auf synced und status auf invited, also zeichnete die Spalte "Eingeladen" — fuer jemanden, den niemand eingeladen hat und dessen Konto im Gast weiter gesperrt ist. nc_state geht beim Wiederaufnehmen zurueck auf none; nc_username und nc_synced_at bleiben, denn an letzterem haengt das Entsperren beim folgenden Einladen. Eine Pruefung geht den ganzen Weg.feat/versandtakt
parent
02a5120b9c
commit
3704620bb5
|
|
@ -132,16 +132,27 @@ class Users extends Component
|
|||
}
|
||||
|
||||
if ($vorhanden !== null) {
|
||||
// `nc_username` bleibt, wie er ist: Nextcloud kann Benutzer
|
||||
// nicht umbenennen, und es ist dieselbe Person. `nc_state`
|
||||
// ebenfalls — was im Gast steht, aendert sich durch das
|
||||
// Anlegen nie, das ist der ganze Sinn der Trennung.
|
||||
$vorhanden->update([
|
||||
// `nc_username` und `nc_synced_at` bleiben, wie sie sind: der
|
||||
// Anmeldename gehoert weiter dieser Person (Nextcloud kann
|
||||
// nicht umbenennen), und dass das Konto im Gast existiert,
|
||||
// bleibt wahr — daran haengt spaeter das Entsperren.
|
||||
//
|
||||
// `nc_state` dagegen MUSS zurueck auf `none`: der Sitz stand
|
||||
// auf `synced`, und `status = 'invited'` daneben liesse die
|
||||
// Zeile „Eingeladen" zeichnen — fuer jemanden, den niemand
|
||||
// eingeladen hat und dessen Konto im Gast weiter gesperrt ist.
|
||||
// Dieselbe Luege wie eine Zeile, die einen Fehlschlag
|
||||
// verschweigt, nur in die andere Richtung. Mit `none` heisst
|
||||
// sie „angelegt — noch nicht eingeladen", der Knopf heisst
|
||||
// „Einladen", und beides stimmt.
|
||||
$vorhanden->forceFill([
|
||||
'name' => $data['inviteName'] ?: $vorhanden->name,
|
||||
'role' => $data['inviteRole'],
|
||||
'status' => 'invited',
|
||||
'invited_at' => now(),
|
||||
]);
|
||||
'nc_state' => Seat::STATE_NONE,
|
||||
'nc_error' => null,
|
||||
])->save();
|
||||
|
||||
return 'reinstated';
|
||||
}
|
||||
|
|
@ -295,6 +306,21 @@ class Users extends Component
|
|||
default => 'role',
|
||||
};
|
||||
|
||||
// Derselbe Vorbehalt wie in queueSync(), und er fehlte hier: war dieser
|
||||
// Sitz nie in der Nextcloud, gibt es dort nichts zu sperren. Ohne die
|
||||
// Zeile drehte sich der Knopf an einer entzogenen, nie gespiegelten
|
||||
// Zeile im Kreis — anlegen, einladen, Auftrag scheitert an
|
||||
// `no_instance`, entziehen (queueSync bricht korrekt ab, `failed`
|
||||
// bleibt stehen), und jeder Druck schickte ein `user:disable` gegen
|
||||
// einen Benutzer, den es nie gab. Genau der Dauerfehler, den
|
||||
// queueSync() schon abgestellt hat, nur ueber die andere Tuer.
|
||||
//
|
||||
// `invite` ist ausgenommen: das ist der Fall, FUER den dieser Knopf
|
||||
// gebaut ist — dort soll das Konto ja erst entstehen.
|
||||
if ($action !== 'invite' && $seat->nc_synced_at === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$seat->forceFill(['nc_state' => Seat::STATE_PENDING, 'nc_error' => null])->save();
|
||||
SyncSeatToNextcloud::dispatch($seat->uuid, $action);
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,17 @@
|
|||
Inhaber nichts, und ein Fehlschlag
|
||||
ohne Grund ist eine Sackgasse. --}}
|
||||
<p class="mt-1 text-xs text-muted">{{ __('users.error_'.($seat->nc_error ?: 'unexpected')) }}</p>
|
||||
@if ($seat->role !== 'owner')
|
||||
@php
|
||||
// Dieselbe Bedingung wie in retry(): ein Knopf, der
|
||||
// nur wieder rot zurueckkommt, ist schlimmer als
|
||||
// keiner. Eine entzogene oder gesperrte Zeile, die
|
||||
// nie in der Nextcloud ankam, hat dort nichts zu
|
||||
// sperren — da gaebe es nichts zu wiederholen.
|
||||
$wiederholbar = $seat->role !== 'owner'
|
||||
&& ($seat->nc_synced_at !== null
|
||||
|| ! in_array($seat->status, ['revoked', 'suspended'], true));
|
||||
@endphp
|
||||
@if ($wiederholbar)
|
||||
<button type="button" wire:click="retry('{{ $seat->uuid }}')" class="mt-1 text-xs underline">
|
||||
{{ __('users.retry') }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -471,6 +471,30 @@ it('wiederholt an einem entzogenen Sitz das Sperren, nie das Einladen', function
|
|||
expect($sitz->fresh()->status)->toBe('revoked');
|
||||
});
|
||||
|
||||
it('wiederholt nichts an einer entzogenen Zeile, die nie in der Nextcloud war', function () {
|
||||
// Anlegen → einladen → Auftrag scheitert an `no_instance` → entziehen.
|
||||
// queueSync() bricht korrekt ab, `failed` bleibt stehen, und der
|
||||
// Fehlschlag-Block ist zu Recht da. Ohne Vorbehalt liefe ein Druck aber auf
|
||||
// `user:disable` gegen einen Benutzer, den es nie gab: scheitert, dieselbe
|
||||
// rote Zeile, beliebig oft. Genau der Dauerfehler, nur ueber die andere
|
||||
// Tuer — und mit einem veralteten Grund aus einem anderen Vorgang.
|
||||
Queue::fake();
|
||||
$customer = Customer::factory()->create();
|
||||
$user = $customer->ensureUser();
|
||||
Instance::factory()->for($customer)->create(['status' => 'active']);
|
||||
$sitz = Seat::factory()->for($customer)->create([
|
||||
'role' => 'member', 'status' => 'revoked', 'nc_username' => 'anna@firma.tld',
|
||||
'nc_state' => Seat::STATE_FAILED, 'nc_error' => 'no_instance', 'nc_synced_at' => null,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($user)->test(Users::class)
|
||||
->call('retry', $sitz->uuid)
|
||||
// Und der Knopf steht dort gar nicht erst.
|
||||
->assertDontSee("retry('{$sitz->uuid}')", escape: false);
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
});
|
||||
|
||||
it('wiederholt nichts an einem Inhaber-Sitz', function () {
|
||||
// retry() war der ungehaertete Zwilling von sendInvite(): an einem
|
||||
// beschaedigten Inhaber-Sitz (failed, nie gespiegelt) waehlte es die Aktion
|
||||
|
|
@ -520,6 +544,48 @@ it('nimmt einen entzogenen Sitz ueber das Anlegen-Formular wieder auf', function
|
|||
Queue::assertNothingPushed();
|
||||
});
|
||||
|
||||
it('laesst einen wieder Aufgenommenen auch wirklich wieder hinein', function () {
|
||||
// Der ganze Weg in einer Pruefung: entziehen, wieder aufnehmen, einladen.
|
||||
// Zwei Dinge muessen dabei zugleich stimmen — die Zeile darf nicht
|
||||
// „Eingeladen" behaupten, solange niemand eingeladen hat, und die Sperre,
|
||||
// die revoke() in den Gast geschickt hat, muss beim Einladen wieder
|
||||
// aufgehen. `user:welcome` allein tut das nicht.
|
||||
Queue::fake();
|
||||
$pve = new FakeProxmoxClient;
|
||||
app()->instance(ProxmoxClient::class, $pve);
|
||||
$customer = Customer::factory()->create();
|
||||
$user = $customer->ensureUser();
|
||||
Instance::factory()->for($customer)->create(['status' => 'active', 'vmid' => 201, 'host_id' => Host::factory()]);
|
||||
$sitz = Seat::factory()->for($customer)->create([
|
||||
'email' => 'anna@firma.tld', 'role' => 'member', 'status' => 'active',
|
||||
'nc_username' => 'anna@firma.tld', 'nc_state' => Seat::STATE_SYNCED, 'nc_synced_at' => now()->subYear(),
|
||||
]);
|
||||
|
||||
$seite = Livewire::actingAs($user)->test(Users::class);
|
||||
$seite->call('revoke', $sitz->uuid);
|
||||
$seite->set('inviteEmail', 'anna@firma.tld')->set('inviteRole', 'member')
|
||||
->call('addSeat')->assertHasNoErrors();
|
||||
|
||||
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_NONE)
|
||||
->and($sitz->fresh()->nc_error)->toBeNull()
|
||||
// Anmeldename und Ankunftszeitpunkt bleiben: der Name gehoert dieser
|
||||
// Person, und dass es das Konto im Gast gibt, bleibt wahr.
|
||||
->and($sitz->fresh()->nc_username)->toBe('anna@firma.tld')
|
||||
->and($sitz->fresh()->nc_synced_at)->not->toBeNull();
|
||||
|
||||
// Und die Zeile sagt es auch: „angelegt — noch nicht eingeladen", mit dem
|
||||
// Knopf „Einladen" statt „Erneut senden".
|
||||
$seite->assertSee(__('users.state_none'))
|
||||
->assertSee(__('users.invite'))
|
||||
->assertDontSee(__('users.status_invited'));
|
||||
|
||||
$seite->call('sendInvite', $sitz->uuid);
|
||||
(new SyncSeatToNextcloud($sitz->uuid, 'invite'))->handle(app(NextcloudUsers::class));
|
||||
|
||||
expect($pve->guestRan('user:enable'))->toBeTrue()
|
||||
->and($sitz->fresh()->nc_state)->toBe(Seat::STATE_SYNCED);
|
||||
});
|
||||
|
||||
it('weist die Wiederaufnahme ab, wenn kein Platz mehr frei ist', function () {
|
||||
// Der Grund, warum es die Vordertuer sein muss und kein Knopf an der Zeile:
|
||||
// hier steht die Platzpruefung. Ein entzogener Sitz zaehlt nicht mit, ein
|
||||
|
|
|
|||
Loading…
Reference in New Issue