/**
 * @license
 * Copyright 2017 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { IndexConfiguration } from '../../../src/api/index_configuration';
import { Query } from '../../../src/core/query';
import { Target } from '../../../src/core/target';
import { TargetIdGenerator } from '../../../src/core/target_id_generator';
import { TargetId } from '../../../src/core/types';
import { TargetPurpose } from '../../../src/local/target_data';
import { Document } from '../../../src/model/document';
import { DocumentKey } from '../../../src/model/document_key';
import { FieldIndex } from '../../../src/model/field_index';
import { JsonObject } from '../../../src/model/object_value';
import { BloomFilter as ProtoBloomFilter } from '../../../src/protos/firestore_proto_api';
import { TimerId } from '../../../src/util/async_queue';
import { Code } from '../../../src/util/error';
import { ObjectMap } from '../../../src/util/obj_map';
import { TestSnapshotVersion } from '../../util/helpers';
import { RpcError } from './spec_rpc_error';
import { PersistenceAction, SpecConfig, SpecQuery, SpecStep } from './spec_test_runner';
export interface LimboMap {
    [key: string]: TargetId;
}
export interface ActiveTargetSpec {
    queries: SpecQuery[];
    targetPurpose?: TargetPurpose;
    resumeToken?: string;
    readTime?: TestSnapshotVersion;
    expectedCount?: number;
}
export interface ActiveTargetMap {
    [targetId: string]: ActiveTargetSpec;
}
export interface ResumeSpec {
    resumeToken?: string;
    readTime?: TestSnapshotVersion;
    expectedCount?: number;
}
/**
 * Tracks the expected memory state of a client (e.g. the expected active watch
 * targets based on userListens(), userUnlistens(), and watchRemoves()
 * as well as the expectActiveTargets() and expectLimboDocs() expectations).
 *
 * Automatically keeping track of the active targets makes writing tests
 * much simpler and the tests much easier to follow.
 *
 * Whenever the map changes, the expected state is automatically encoded in
 * the tests.
 */
export declare class ClientMemoryState {
    activeTargets: ActiveTargetMap;
    queryMapping: ObjectMap<Target, number>;
    limboMapping: LimboMap;
    limboIdGenerator: TargetIdGenerator;
    injectFailures: boolean;
    constructor();
    /** Reset all internal memory state (as done during a client restart). */
    reset(): void;
    /**
     * Reset the internal limbo mapping (as done during a primary lease failover).
     */
    resetLimboMapping(): void;
}
/**
 * Provides a high-level language to construct spec tests that can be exported
 * to the spec JSON format or be run as a spec test directly.
 *
 * Exported JSON tests can be used in other clients without the need to
 * duplicate tests in every client.
 */
export declare class SpecBuilder {
    protected config: SpecConfig;
    protected currentStep: SpecStep | null;
    private steps;
    private queryIdGenerator;
    private readonly currentClientState;
    protected get clientState(): ClientMemoryState;
    private get limboIdGenerator();
    private get queryMapping();
    private get limboMapping();
    private get activeTargets();
    private get injectFailures();
    private set injectFailures(value);
    /**
     * Exports the spec steps as a JSON object that be used in the spec runner.
     */
    toJSON(): {
        config: SpecConfig;
        steps: SpecStep[];
    };
    /**
     * Run the spec as a test. If persistence is available it will run it with and
     * without persistence enabled.
     */
    runAsTest(name: string, tags: string[], usePersistence: boolean): Promise<void>;
    ensureManualLruGC(): this;
    withMaxConcurrentLimboResolutions(value?: number): this;
    private addUserListenStep;
    userListens(query: Query, resume?: ResumeSpec): this;
    userListensToCache(query: Query, resume?: ResumeSpec): this;
    /**
     * Registers a previously active target with the test expectations after a
     * stream disconnect.
     */
    restoreListen(query: Query, resumeToken: string, expectedCount?: number): this;
    userUnlistens(query: Query, shouldRemoveWatchTarget?: boolean): this;
    userUnlistensToCache(query: Query): this;
    userSets(key: string, value: JsonObject<unknown>): this;
    userPatches(key: string, value: JsonObject<unknown>): this;
    userDeletes(key: string): this;
    userAddsSnapshotsInSyncListener(): this;
    userRemovesSnapshotsInSyncListener(): this;
    loadBundle(bundleContent: string): this;
    setIndexConfiguration(jsonOrConfiguration: string | IndexConfiguration): this;
    becomeHidden(): this;
    becomeVisible(): this;
    runTimer(timerId: TimerId): this;
    changeUser(uid: string | null): this;
    disableNetwork(): this;
    enableNetwork(): this;
    clearPersistence(): this;
    restart(): this;
    shutdown(): this;
    /**
     * Fails the specified database transaction until `recoverDatabase()` is
     * called.
     */
    failDatabaseTransactions(...actions: PersistenceAction[]): this;
    /** Stops failing database operations. */
    recoverDatabase(): this;
    expectIsShutdown(): this;
    /** Expects indexes to exist (in any order) */
    expectIndexes(indexes: FieldIndex[]): this;
    /** Overrides the currently expected set of active targets. */
    expectActiveTargets(...targets: Array<{
        query: Query;
        targetPurpose?: TargetPurpose;
        resumeToken?: string;
        readTime?: TestSnapshotVersion;
        expectedCount?: number;
    }>): this;
    /**
     * Expects a document to be in limbo. A targetId is assigned if it's not in
     * limbo yet.
     */
    expectLimboDocs(...keys: DocumentKey[]): this;
    /**
     * Expects a document to be in limbo, enqueued for limbo resolution, and
     * therefore *without* an active targetId.
     */
    expectEnqueuedLimboDocs(...keys: DocumentKey[]): this;
    /**
     * Special helper for limbo documents that acks with either a document or
     * with no document for NoDocument. This is translated into normal watch
     * messages.
     */
    ackLimbo(version: TestSnapshotVersion, doc: Document): this;
    /**
     * Special helper for limbo documents that acks an unlisten for a limbo doc
     * with either a document or with no document for NoDocument. This is
     * translated into normal watch messages.
     */
    watchRemovesLimboTarget(doc: Document): this;
    /**
     * Acks a write with a version and optional additional options.
     *
     * expectUserCallback defaults to true if omitted.
     */
    writeAcks(doc: string, version: TestSnapshotVersion, options?: {
        expectUserCallback?: boolean;
        keepInQueue?: boolean;
    }): this;
    /**
     * Fails a write with an error and optional additional options.
     *
     * expectUserCallback defaults to true if omitted.
     */
    failWrite(doc: string, error: RpcError, options?: {
        expectUserCallback?: boolean;
        keepInQueue?: boolean;
    }): this;
    watchAcks(query: Query): this;
    watchCurrents(query: Query, resumeToken: string): this;
    watchRemoves(query: Query, cause?: RpcError): this;
    watchSends(targets: {
        affects?: Query[];
        removed?: Query[];
    }, ...docs: Document[]): this;
    watchRemovesDoc(key: DocumentKey, ...targets: Query[]): this;
    watchFilters(queries: Query[], docs?: DocumentKey[], bloomFilter?: ProtoBloomFilter): this;
    watchResets(...queries: Query[]): this;
    watchSnapshots(version: TestSnapshotVersion, targets?: Query[], resumeToken?: string): this;
    watchAcksFull(query: Query, version: TestSnapshotVersion, ...docs: Document[]): this;
    watchStreamCloses(error: Code, opts?: {
        runBackoffTimer: boolean;
    }): this;
    waitForPendingWrites(): this;
    expectUserCallbacks(docs: {
        acknowledged?: string[];
        rejected?: string[];
    }): this;
    expectEvents(query: Query, events: {
        fromCache?: boolean;
        hasPendingWrites?: boolean;
        added?: Document[];
        modified?: Document[];
        removed?: Document[];
        metadata?: Document[];
        errorCode?: Code;
    }): this;
    private registerQuery;
    /** Registers a query that is active in another tab. */
    expectListen(query: Query, resume?: ResumeSpec): this;
    /** Registers a query that is listening to cache and active in another tab. */
    expectListenToCache(query: Query, resume?: ResumeSpec): this;
    removeQuery(query: Query, shouldRemoveWatchTarget?: boolean): this;
    /** Removes a query that is no longer active in any tab. */
    expectUnlisten(query: Query): this;
    /** Removes a query that is listening to cache and no longer active in any tab. */
    expectUnlistenToCache(query: Query): this;
    /**
     * Verifies the total number of requests sent to the write backend since test
     * initialization.
     */
    expectWriteStreamRequestCount(num: number): this;
    /**
     * Verifies the total number of requests sent to the watch backend since test
     * initialization.
     */
    expectWatchStreamRequestCount(num: number): this;
    expectNumOutstandingWrites(num: number): this;
    expectNumActiveClients(num: number): this;
    expectPrimaryState(isPrimary: boolean): this;
    expectSnapshotsInSyncEvent(count?: number): this;
    expectWaitForPendingWritesEvent(count?: number): this;
    triggerLruGC(cacheThreshold: number): this;
    removeExpectedTargetMapping(query: Query): this;
    private static queryToSpec;
    private static docToSpec;
    private static keyToSpec;
    protected nextStep(): void;
    /**
     * Add the specified `Query` under give active targe id. If it is already
     * added, this is a no-op.
     */
    private addQueryToActiveTargets;
    private removeQueryFromActiveTargets;
    private assertStep;
    private getTargetId;
}
/**
 * SpecBuilder that supports serialized interactions between different clients.
 *
 * Use `client(clientIndex)` to switch between clients.
 */
export declare class MultiClientSpecBuilder extends SpecBuilder {
    private activeClientIndex;
    private clientStates;
    protected get clientState(): ClientMemoryState;
    client(clientIndex: number): MultiClientSpecBuilder;
    /**
     * Take the primary lease, even if another client has already obtained the
     * lease.
     */
    stealPrimaryLease(): this;
    protected nextStep(): void;
}
/** Starts a new single-client SpecTest. */
export declare function spec(): SpecBuilder;
/** Starts a new multi-client SpecTest. */
export declare function client(num: number): MultiClientSpecBuilder;
