ChromeOS tables: Errors surfaced in Fleet UI (#12376)

This commit is contained in:
RachelElysia 2023-09-19 10:06:29 -04:00 committed by GitHub
parent 414c2f42b3
commit 9c5d7faa58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 308 additions and 196 deletions

View File

@ -0,0 +1 @@
- UI improvement: Surface chrome live query errors to Fleet UI (including errors for specific columns while maintaining successful data in results)

View File

@ -12,6 +12,7 @@ interface requestArgs {
body?: Record<string, any>;
reenroll?: boolean;
}
const request = async ({ path, body = {} }: requestArgs): Promise<any> => {
const { fleet_url } = await chrome.storage.managed.get({
fleet_url: FLEET_URL,
@ -67,8 +68,8 @@ const authenticatedRequest = async ({
};
const enroll = async () => {
const os_version = await DATABASE.query("SELECT * FROM os_version");
const system_info = await DATABASE.query("SELECT * FROM system_info");
const os_version = (await DATABASE.query("SELECT * FROM os_version")).data;
const system_info = (await DATABASE.query("SELECT * FROM system_info")).data;
const host_details = {
os_version: os_version[0],
system_info: system_info[0],
@ -118,7 +119,8 @@ const live_query = async () => {
const query_discovery_sql = response.discovery[query_name];
if (query_discovery_sql) {
try {
const discovery_result = await DATABASE.query(query_discovery_sql);
const discovery_result = (await DATABASE.query(query_discovery_sql))
.data;
if (discovery_result.length == 0) {
// Discovery queries that return no results mean skip running the query.
continue;
@ -129,6 +131,9 @@ const live_query = async () => {
console.debug(
`Discovery (${query_name} sql: "${query_discovery_sql}") failed: ${err}`
);
results[query_name] = null;
statuses[query_name] = 1;
messages[query_name] = err.toString();
continue;
}
}
@ -137,8 +142,12 @@ const live_query = async () => {
const query_sql = response.queries[query_name];
try {
const query_result = await DATABASE.query(query_sql);
results[query_name] = query_result;
results[query_name] = query_result.data;
statuses[query_name] = 0;
if (query_result.warnings.length !== 0) {
statuses[query_name] = 1; // Set to show warnings in errors table and campaign.ts returned host_counts to +1 failing instead of +1 successful
messages[query_name] = query_result.warnings; // Warnings array is concatenated in Table.ts xfilter
}
} catch (err) {
console.warn(`Query (${query_name} sql: "${query_sql}") failed: ${err}`);
results[query_name] = null;

View File

@ -15,9 +15,20 @@ import TableSystemInfo from "./tables/system_info";
import TableSystemState from "./tables/system_state";
import TableUsers from "./tables/users";
interface ChromeWarning {
column: string;
error_message: string;
}
interface ChromeResponse {
data: Record<string, string>[];
/** Manually add errors in catch response if table requires multiple APIs requests */
warnings?: ChromeWarning[];
}
export default class VirtualDatabase {
sqlite3: SQLiteAPI;
db: number;
warnings?: ChromeWarning[];
private constructor(sqlite3: SQLiteAPI, db: number) {
this.sqlite3 = sqlite3;
@ -42,7 +53,11 @@ export default class VirtualDatabase {
new TablePrivacyPreferences(sqlite3, db)
);
VirtualDatabase.register(sqlite3, db, new TableScreenLock(sqlite3, db));
VirtualDatabase.register(sqlite3, db, new TableSystemInfo(sqlite3, db));
VirtualDatabase.register(
sqlite3,
db,
new TableSystemInfo(sqlite3, db, this.warnings)
);
VirtualDatabase.register(sqlite3, db, new TableSystemState(sqlite3, db));
VirtualDatabase.register(sqlite3, db, new TableOSVersion(sqlite3, db));
VirtualDatabase.register(sqlite3, db, new TableOsqueryInfo(sqlite3, db));
@ -60,7 +75,7 @@ export default class VirtualDatabase {
sqlite3.create_module(db, table.name, table);
}
async query(sql: string): Promise<Record<string, string | number>[]> {
async query(sql: string): Promise<ChromeResponse> {
let rows = [];
await this.sqlite3.exec(this.db, sql, (row, columns) => {
// map each row to object
@ -68,6 +83,6 @@ export default class VirtualDatabase {
Object.fromEntries(columns.map((_, i) => [columns[i], row[i]]))
);
});
return rows;
return { data: rows, warnings: this.warnings };
}
}

View File

@ -4,29 +4,48 @@
import * as SQLite from "wa-sqlite";
/** Creates a single UI friendly string out of chrome tables that return multiple warnings */
const CONCAT_CHROME_WARNINGS = (warnings: ChromeWarning[]): string => {
const warningStrings = warnings.map(
(warning) => `Column: ${warning.column} - ${warning.error_message}`
);
return warningStrings.join("\n");
};
class cursorState {
rowIndex: number;
rows: Record<string, string>[];
error: any;
}
interface ChromeWarning {
column: string;
error_message: string;
}
interface ChromeResponse {
data: Record<string, string>[];
/** Manually add errors in catch response if table requires requests to multiple APIs */
warnings?: ChromeWarning[];
}
export default abstract class Table implements SQLiteModule {
sqlite3: SQLiteAPI;
db: number;
name: string;
columns: string[];
cursorStates: Map<number, cursorState>;
warnings?: ChromeWarning[];
abstract generate(
idxNum: number,
idxString: string,
values: Array<number>
): Promise<Record<string, string>[]>;
): Promise<ChromeResponse>;
constructor(sqlite3: SQLiteAPI, db: number) {
constructor(sqlite3: SQLiteAPI, db: number, warnings?: ChromeWarning[]) {
this.sqlite3 = sqlite3;
this.db = db;
this.cursorStates = new Map();
this.warnings = warnings;
}
// This is replaced by wa-sqlite when SQLite is loaded up, but missing from the SQLiteModule
@ -91,10 +110,19 @@ export default abstract class Table implements SQLiteModule {
const cursorState = this.cursorStates.get(pCursor);
cursorState.rowIndex = 0;
try {
cursorState.rows = await this.generate(idxNum, idxStr, values);
const tableDataReturned = await this.generate(idxNum, idxStr, values);
// Set warnings to this.warnings for database to surface in UI
if (tableDataReturned.warnings) {
globalThis.DB.warnings = []; // Reset warnings
globalThis.DB.warnings = CONCAT_CHROME_WARNINGS(
tableDataReturned.warnings
);
}
cursorState.rows = tableDataReturned.data;
} catch (err) {
// Throwing here doesn't seem to work as expected in testing (the error doesn't seem to be
// thrown in away that it can be caught appropriately), so instead we save the error and
// thrown in a way that it can be caught appropriately), so instead we save the error and
// throw in xEof.
cursorState.error = err;
}

View File

@ -36,6 +36,6 @@ export default class TableChromeExtensions extends Table {
});
}
return rows;
return { data: rows };
}
}

View File

@ -1,19 +1,21 @@
import VirtualDatabase from "../db";
const DISK_INFO_MOCK = [
{
capacity: 1234,
id: 123,
name: "Cell phone (internal storage",
type: "Removable",
},
{
capacity: 0,
id: 12,
name: "Thumbdrive",
type: "Removable",
},
];
const DISK_INFO_MOCK = {
data: [
{
capacity: 1234,
id: 123,
name: "Cell phone (internal storage",
type: "Removable",
},
{
capacity: 0,
id: 12,
name: "Thumbdrive",
type: "Removable",
},
],
};
describe("disk_info", () => {
test("success", async () => {

View File

@ -15,6 +15,6 @@ export default class TableDiskInfo extends Table {
type: d.type,
});
}
return rows;
return { data: rows };
}
}

View File

@ -45,14 +45,16 @@ describe("geolocation", () => {
})
);
const rows = await db.query("select * from geolocation");
expect(rows).toEqual([
{
ip: "260f:1337:4a7e:e300:abcd:a98a:1234:18c",
city: "Vancouver",
country: "Canada",
region: "British Columbia",
},
]);
expect(rows).toEqual({
data: [
{
ip: "260f:1337:4a7e:e300:abcd:a98a:1234:18c",
city: "Vancouver",
country: "Canada",
region: "British Columbia",
},
],
});
});
test("request returns incomplete data", async () => {
@ -66,14 +68,16 @@ describe("geolocation", () => {
})
);
const rows = await db.query("select * from geolocation");
expect(rows).toEqual([
{
ip: null,
city: "Vancouver",
country: null,
region: null,
},
]);
expect(rows).toEqual({
data: [
{
ip: null,
city: "Vancouver",
country: null,
region: null,
},
],
});
});
test("request fails", async () => {

View File

@ -14,13 +14,15 @@ export default class TableGeolocation extends Table {
async generate() {
const resp = await fetch("https://ipapi.co/json");
const json = await resp.json();
return [
{
ip: this.ensureString(json.ip),
city: this.ensureString(json.city),
country: this.ensureString(json.country_name),
region: this.ensureString(json.region),
},
];
return {
data: [
{
ip: this.ensureString(json.ip),
city: this.ensureString(json.city),
country: this.ensureString(json.country_name),
region: this.ensureString(json.region),
},
],
};
}
}

View File

@ -5,22 +5,20 @@ export default class TableNetworkInterfaces extends Table {
columns = ["mac", "ipv4", "ipv6"];
async generate() {
let ipv4: string, ipv6: string, mac: string;
try {
// @ts-expect-error @types/chrome doesn't yet have the getNetworkDetails Promise API.
const networkDetails = (await chrome.enterprise.networkingAttributes.getNetworkDetails()) as chrome.enterprise.networkingAttributes.NetworkDetails;
ipv4 = networkDetails.ipv4;
ipv6 = networkDetails.ipv6;
mac = networkDetails.macAddress;
} catch (err) {
console.warn(`get network details: ${err}`);
}
return [
{
mac,
ipv4,
ipv6,
},
];
// @ts-expect-error @types/chrome doesn't yet have the getNetworkDetails Promise API.
const networkDetails = (await chrome.enterprise.networkingAttributes.getNetworkDetails()) as chrome.enterprise.networkingAttributes.NetworkDetails;
const ipv4 = networkDetails.ipv4;
const ipv6 = networkDetails.ipv6;
const mac = networkDetails.macAddress;
return {
data: [
{
mac,
ipv4,
ipv6,
},
],
};
}
}

View File

@ -3,18 +3,18 @@ import TableOSVersion from "./os_version";
describe("os_version", () => {
describe("getName", () => {
const sut = new TableOSVersion(null, null)
const sut = new TableOSVersion(null, null);
it("returns platform name properly formatted", () => {
expect(sut.getName("Chrome OS")).toBe("ChromeOS")
})
})
expect(sut.getName("Chrome OS")).toBe("ChromeOS");
});
});
describe("getCodename", () => {
const sut = new TableOSVersion(null, null)
const sut = new TableOSVersion(null, null);
it("has the proper prefix", () => {
expect(sut.getCodename("10.0.0").startsWith("ChromeOS")).toBe(true)
})
})
expect(sut.getCodename("10.0.0").startsWith("ChromeOS")).toBe(true);
});
});
test("success", async () => {
// @ts-expect-error Typescript doesn't include the userAgentData API yet.
@ -40,20 +40,22 @@ describe("os_version", () => {
const db = await VirtualDatabase.init();
const res = await db.query("select * from os_version");
expect(res).toEqual([
{
name: "ChromeOS",
platform: "chrome",
platform_like: "chrome",
version: "110.0.5481.177",
major: "110",
minor: "0",
build: "5481",
patch: "177",
arch: "x86-64",
codename: "ChromeOS 13.2.1",
},
]);
expect(res).toEqual({
data: [
{
name: "ChromeOS",
platform: "chrome",
platform_like: "chrome",
version: "110.0.5481.177",
major: "110",
minor: "0",
build: "5481",
patch: "177",
arch: "x86-64",
codename: "ChromeOS 13.2.1",
},
],
});
});
test("unexpected version string", async () => {
@ -81,20 +83,22 @@ describe("os_version", () => {
const db = await VirtualDatabase.init();
const res = await db.query("select * from os_version");
expect(res).toEqual([
{
name: "ChromeOS",
platform: "chrome",
platform_like: "chrome",
version: "110.weird_version",
major: "",
minor: "",
build: "",
patch: "",
arch: "x86-64",
codename: "ChromeOS 13.2.1",
},
]);
expect(res).toEqual({
data: [
{
name: "ChromeOS",
platform: "chrome",
platform_like: "chrome",
version: "110.weird_version",
major: "",
minor: "",
build: "",
patch: "",
arch: "x86-64",
codename: "ChromeOS 13.2.1",
},
],
});
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining("expected 4 segments")
);
@ -105,9 +109,11 @@ describe("os_version", () => {
global.navigator.userAgentData = {
getHighEntropyValues: jest.fn(() =>
Promise.resolve({
fullVersionList: [
{ brand: "Not even chrome", version: "110.0.5481.177" },
],
data: {
fullVersionList: [
{ brand: "Not even chrome", version: "110.0.5481.177" },
],
},
})
),
};

View File

@ -63,20 +63,22 @@ export default class TableOSVersion extends Table {
const { arch } = platformInfo;
// Some of these values won't actually be correct on a non-chromeOS machine.
return [
{
name: this.getName(data.platform),
platform: "chrome",
platform_like: "chrome",
version,
major,
minor,
build,
patch,
codename: this.getCodename(data.platformVersion),
// https://developer.chrome.com/docs/extensions/reference/runtime/#type-PlatformArch
arch: arch,
},
];
return {
data: [
{
name: this.getName(data.platform),
platform: "chrome",
platform_like: "chrome",
version,
major,
minor,
build,
patch,
codename: this.getCodename(data.platformVersion),
// https://developer.chrome.com/docs/extensions/reference/runtime/#type-PlatformArch
arch: arch,
},
],
};
}
}

View File

@ -5,13 +5,15 @@ export default class TableOsqueryInfo extends Table {
columns = ["version", "build_platform", "build_distro", "extensions"];
async generate() {
return [
{
version: `fleetd-chrome-${chrome.runtime.getManifest().version}`,
build_platform: "chrome",
build_distro: "chrome",
extensions: "inactive",
},
];
return {
data: [
{
version: `fleetd-chrome-${chrome.runtime.getManifest().version}`,
build_platform: "chrome",
build_distro: "chrome",
extensions: "inactive",
},
],
};
}
}

View File

@ -88,10 +88,12 @@ export default class TablePrivacyPreferences extends Table {
const columns = await Promise.all(results);
errors.length > 0 &&
console.log("Caught errors in chrome API calls: ", errors);
return [
columns.reduce((resultRow, column) => {
return { ...resultRow, ...column };
}, {}),
];
return {
data: [
columns.reduce((resultRow, column) => {
return { ...resultRow, ...column };
}, {}),
],
};
}
}

View File

@ -7,11 +7,13 @@ describe("screenlock", () => {
const db = await VirtualDatabase.init();
const res = await db.query("select * from screenlock");
expect(res).toEqual([
{
enabled: 1,
grace_period: 600,
},
]);
expect(res).toEqual({
data: [
{
enabled: 1,
grace_period: 600,
},
],
});
});
});

View File

@ -13,7 +13,7 @@ export default class TableScreenLock extends Table {
const enabled = delay > 0 ? "1" : "0";
const gracePeriod = delay > 0 ? delay.toString() : "-1";
return [{ enabled, grace_period: gracePeriod }];
return { data: [{ enabled, grace_period: gracePeriod }] };
}
throw new Error(
"Unexpected response from chrome.idle.getAutoLockDelay - expected number"

View File

@ -1,21 +1,23 @@
import TableSystemInfo from "./system_info";
describe("system_info", () => {
describe("getComputerName", () => {
const sut = new TableSystemInfo(null, null)
it("is computed from the hostname and hw serial", () => {
const testCases: [string, string, string][] = [
[null, null, "Chromebook"],
[undefined, undefined, "Chromebook"],
["", "", "Chromebook"],
["mychromebook", "", "mychromebook"],
["mychromebook", "123", "mychromebook"],
["", "123", "Chromebook 123"],
]
describe("getComputerName", () => {
const sut = new TableSystemInfo(null, null);
it("is computed from the hostname and hw serial", () => {
const testCases: [string, string, string][] = [
[null, null, "Chromebook"],
[undefined, undefined, "Chromebook"],
["", "", "Chromebook"],
["mychromebook", "", "mychromebook"],
["mychromebook", "123", "mychromebook"],
["", "123", "Chromebook 123"],
];
for (let [hostname, hwSerial, expected] of testCases) {
expect(sut.getComputerName(hostname, hwSerial)).toEqual(expected)
}
})
})
})
for (let [hostname, hwSerial, expected] of testCases) {
expect(sut.getComputerName(hostname, hwSerial)).toEqual({
data: expected,
});
}
});
});
});

View File

@ -29,6 +29,8 @@ export default class TableSystemInfo extends Table {
}
async generate() {
let warningsArray = [];
// @ts-expect-error @types/chrome doesn't yet have instanceID.
const uuid = (await chrome.instanceID.getID()) as string;
@ -39,14 +41,22 @@ export default class TableSystemInfo extends Table {
hostname = (await chrome.enterprise.deviceAttributes.getDeviceHostname()) as string;
} catch (err) {
console.warn("get hostname:", err);
warningsArray.push({
column: "hostname",
error_message: err.message.toString(),
});
}
let hwSerial = "";
try {
// @ts-expect-error @types/chrome doesn't yet have the deviceAttributes Promise API.
hwSerial = await chrome.enterprise.deviceAttributes.getDeviceSerialNumber();
hwSerial = (await chrome.enterprise.deviceAttributes.getDeviceSerialNumber()) as string;
} catch (err) {
console.warn("get serial number:", err);
warningsArray.push({
column: "hardware_serial",
error_message: err.message.toString(),
});
}
let hwVendor = "",
@ -61,6 +71,14 @@ export default class TableSystemInfo extends Table {
hwModel = platformInfo.model;
} catch (err) {
console.warn("get platform info:", err);
warningsArray.push({
column: "hardware_vendor",
error_message: err.message.toString(),
});
warningsArray.push({
column: "hardware_model",
error_message: err.message.toString(),
});
}
let cpuBrand = "",
@ -71,6 +89,14 @@ export default class TableSystemInfo extends Table {
cpuType = cpuInfo.archName;
} catch (err) {
console.warn("get cpu info:", err);
warningsArray.push({
column: "cpu_brand",
error_message: err.message.toString(),
});
warningsArray.push({
column: "cpu_type",
error_message: err.message.toString(),
});
}
let physicalMemory = "";
@ -79,20 +105,27 @@ export default class TableSystemInfo extends Table {
physicalMemory = memoryInfo.capacity.toString();
} catch (err) {
console.warn("get memory info:", err);
warningsArray.push({
column: "physical_memory",
error_message: err.message.toString(),
});
}
return [
{
uuid,
hostname,
computer_name: this.getComputerName(hostname, hwSerial),
hardware_serial: hwSerial,
hardware_vendor: hwVendor,
hardware_model: hwModel,
cpu_brand: cpuBrand,
cpu_type: cpuType,
physical_memory: physicalMemory,
},
];
return {
data: [
{
uuid,
hostname,
computer_name: this.getComputerName(hostname, hwSerial),
hardware_serial: hwSerial,
hardware_vendor: hwVendor,
hardware_model: hwModel,
cpu_brand: cpuBrand,
cpu_type: cpuType,
physical_memory: physicalMemory,
},
],
warnings: warningsArray,
};
}
}

View File

@ -8,10 +8,12 @@ describe("screenlock", () => {
const db = await VirtualDatabase.init();
const res = await db.query("select * from system_state");
expect(res).toEqual([
{
idle_state: "active",
},
]);
expect(res).toEqual({
data: [
{
idle_state: "active",
},
],
});
});
});

View File

@ -24,6 +24,6 @@ export default class TableSystemState extends Table {
}
);
return [{ idle_state: idleState }];
return { data: [{ idle_state: idleState }] };
}
}

View File

@ -6,6 +6,6 @@ export default class TableUsers extends Table {
async generate() {
const { email, id } = await chrome.identity.getProfileUserInfo({});
return [{ uid: id, email, username: email }];
return { data: [{ uid: id, email, username: email }] };
}
}

View File

@ -4,7 +4,7 @@ import { IHost } from "./host";
export default PropTypes.shape({
hosts_count: PropTypes.shape({
total: PropTypes.number,
successful: PropTypes.number,
successful: PropTypes.number, // Does not include ChromeOS results that are partially successful
failed: PropTypes.number,
}),
id: PropTypes.number,
@ -26,7 +26,7 @@ export interface ICampaign {
hosts: IHost[];
hosts_count: {
total: number;
successful: number;
successful: number; // Does not include ChromeOS results that are partially successful
failed: number;
};
id: number;

View File

@ -213,8 +213,7 @@ const QueryResults = ({
// TODO - clean up these conditions
const hasNoResultsYet =
!isQueryFinished && (!queryResults?.length || tableHeaders === null);
const finishedWithNoResults =
isQueryFinished && (!queryResults?.length || !hostsCount.successful);
const finishedWithNoResults = isQueryFinished && !queryResults?.length;
if (hasNoResultsYet) {
return <AwaitingResults />;

View File

@ -1,5 +1,4 @@
.query-results {
&__results-cta {
display: flex;
}
@ -52,4 +51,8 @@
&__error-table-container {
margin-top: 32px;
}
.error__cell {
white-space: pre-wrap; // Converts \n into new lines
}
}

View File

@ -1079,7 +1079,7 @@ func (svc *Service) ingestQueryResults(
var err error
switch {
case strings.HasPrefix(query, hostDistributedQueryPrefix):
err = svc.ingestDistributedQuery(ctx, *host, query, rows, failed, messages[query])
err = svc.ingestDistributedQuery(ctx, *host, query, rows, messages[query])
case strings.HasPrefix(query, hostPolicyQueryPrefix):
err = ingestMembershipQuery(hostPolicyQueryPrefix, query, rows, policyResults, failed)
case strings.HasPrefix(query, hostLabelQueryPrefix):
@ -1147,7 +1147,7 @@ func (svc *Service) directIngestDetailQuery(ctx context.Context, host *fleet.Hos
// ingestDistributedQuery takes the results of a distributed query and modifies the
// provided fleet.Host appropriately.
func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host, name string, rows []map[string]string, failed bool, errMsg string) error {
func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host, name string, rows []map[string]string, errMsg string) error {
trimmedQuery := strings.TrimPrefix(name, hostDistributedQueryPrefix)
campaignID, err := strconv.Atoi(osquery_utils.EmptyToZero(trimmedQuery))
@ -1165,7 +1165,7 @@ func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host,
},
Rows: rows,
}
if failed {
if errMsg != "" {
res.Error = &errMsg
}

View File

@ -1762,7 +1762,7 @@ func TestIngestDistributedQueryParseIdError(t *testing.T) {
}
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "bad_name", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "bad_name", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "unable to parse campaign")
}
@ -1788,7 +1788,7 @@ func TestIngestDistributedQueryOrphanedCampaignLoadError(t *testing.T) {
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "loading orphaned campaign")
}
@ -1821,7 +1821,7 @@ func TestIngestDistributedQueryOrphanedCampaignWaitListener(t *testing.T) {
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "campaignID=42 waiting for listener")
}
@ -1857,7 +1857,7 @@ func TestIngestDistributedQueryOrphanedCloseError(t *testing.T) {
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "closing orphaned campaign")
}
@ -1894,7 +1894,7 @@ func TestIngestDistributedQueryOrphanedStopError(t *testing.T) {
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "stopping orphaned campaign")
}
@ -1931,7 +1931,7 @@ func TestIngestDistributedQueryOrphanedStop(t *testing.T) {
host := fleet.Host{ID: 1}
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "campaignID=42 stopped")
lq.AssertExpectations(t)
@ -1962,7 +1962,7 @@ func TestIngestDistributedQueryRecordCompletionError(t *testing.T) {
}()
time.Sleep(10 * time.Millisecond)
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "record query completion")
lq.AssertExpectations(t)
@ -1993,7 +1993,7 @@ func TestIngestDistributedQuery(t *testing.T) {
}()
time.Sleep(10 * time.Millisecond)
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
require.NoError(t, err)
lq.AssertExpectations(t)
}