db = Mockery::mock(\wpdb::class); $this->db->prefix = 'wp_'; $this->repo = new InviteRepository($this->db); } public function testInsertReturnsId(): void { Functions\expect('current_time')->with('mysql')->andReturn('2026-06-02 09:00:00'); $this->db->shouldReceive('insert') ->once() ->with( 'wp_us_invites', Mockery::on(static function (array $d): bool { return $d['email'] === 'a@b.test' && $d['token'] === 'tok123' && $d['status'] === Invite::STATUS_PENDING && $d['invited_by'] === 2; }), ['%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s'] ); $this->db->insert_id = 5; self::assertSame(5, $this->repo->insert(new Invite('a@b.test', 'tok123', invitedBy: 2))); } public function testFindByTokenReturnsInvite(): void { $this->db->shouldReceive('prepare') ->once() ->with(Mockery::pattern('/token = %s/'), 'wp_us_invites', 'tok123') ->andReturn('SELECT ...'); $this->db->shouldReceive('get_row')->andReturn($this->row()); $invite = $this->repo->findByToken('tok123'); self::assertInstanceOf(Invite::class, $invite); self::assertSame('a@b.test', $invite->email); } public function testFindByTokenReturnsNullWhenMissing(): void { $this->db->shouldReceive('prepare')->andReturn('SELECT ...'); $this->db->shouldReceive('get_row')->andReturn(null); self::assertNull($this->repo->findByToken('nope')); } public function testFindPendingByEmailFiltersStatus(): void { $this->db->shouldReceive('prepare') ->once() ->with(Mockery::pattern('/email = %s AND status = %s/'), 'wp_us_invites', 'a@b.test', Invite::STATUS_PENDING) ->andReturn('SELECT ...'); $this->db->shouldReceive('get_row')->andReturn($this->row()); self::assertInstanceOf(Invite::class, $this->repo->findPendingByEmail('a@b.test')); } public function testFindPendingMapsRows(): void { $this->db->shouldReceive('prepare') ->once() ->with(Mockery::pattern('/status = %s/'), 'wp_us_invites', Invite::STATUS_PENDING) ->andReturn('SELECT ...'); $this->db->shouldReceive('get_results')->andReturn([$this->row()]); $pending = $this->repo->findPending(); self::assertCount(1, $pending); self::assertInstanceOf(Invite::class, $pending[0]); } public function testMarkAcceptedUpdatesRow(): void { Functions\expect('current_time')->with('mysql')->andReturn('2026-06-02 10:00:00'); $this->db->shouldReceive('update') ->once() ->with( 'wp_us_invites', Mockery::on(static fn (array $d): bool => $d['status'] === Invite::STATUS_ACCEPTED && $d['accepted_user_id'] === 9), ['id' => 5], ['%s', '%d', '%s'], ['%d'] ) ->andReturn(1); self::assertTrue($this->repo->markAccepted(5, 9)); } public function testRevokeUpdatesStatus(): void { $this->db->shouldReceive('update') ->once() ->with('wp_us_invites', ['status' => Invite::STATUS_REVOKED], ['id' => 5], ['%s'], ['%d']) ->andReturn(1); self::assertTrue($this->repo->revoke(5)); } private function row(): object { return (object) [ 'id' => '5', 'email' => 'a@b.test', 'token' => 'tok123', 'role' => 'us_student', 'status' => Invite::STATUS_PENDING, 'invited_by' => '2', 'accepted_user_id' => null, 'accepted_at' => null, ]; } }