Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/test/test-drivers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ export {
OdspDriverApi,
OdspDriverApiType,
} from "./odspDriverApi.js";
export { assertOdspEndpoint, getOdspCredentials, OdspTestDriver } from "./odspTestDriver.js";
export {
assertOdspEndpoint,
getOdspCredentials,
OdspTestDriver,
TenantSetupResult,
UserPassCredentials,
} from "./odspTestDriver.js";
export {
RouterliciousDriverApi,
RouterliciousDriverApiType,
Expand Down
310 changes: 206 additions & 104 deletions packages/test/test-drivers/src/odspTestDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,28 @@ interface IOdspTestDriverConfig extends TokenConfig {
options: HostStoragePolicy | undefined;
}

// specific a range of user name from <prefix><start> to <prefix><start + count - 1> all having the same password
interface LoginTenantRange {
prefix: string;
start: number;
count: number;
password: string;
}

interface LoginTenants {
[tenant: string]: {
range: LoginTenantRange;
// add different format here
};
}
// // specific a range of user name from <prefix><start> to <prefix><start + count - 1> all having the same password
// interface LoginTenantRange {
// prefix: string;
// start: number;
// count: number;
// password: string;
// }

// interface LoginTenants {
// [tenant: string]: {
// range: LoginTenantRange;
// // add different format here
// };
// }

/**
* A simplified version of the credentials returned by the tenant pool containing only username and password values.
* @internal
*/
export interface UserPassCredentials {
UserPrincipalName: string;
Password: string;
username: string;
password: string;
}

/**
Expand All @@ -88,101 +89,173 @@ export function assertOdspEndpoint(
throw new TypeError("Not a odsp endpoint");
}

/**
* @internal
*/
export interface TenantSetupResult {
userPass: UserPassCredentials[];
reservationId: string;
appClientId: string;
}

interface SetupArgs {
waitTime?: number;
accessToken?: string;
odspEndpoint?: "prod" | "dogfood" | "df";
}

interface CleanupArgs {
reservationId?: string;
odspEndpoint?: "prod" | "dogfood" | "df";
}

const importTenantManagerPackage = async (
args: SetupArgs | CleanupArgs,
setup: boolean,
): Promise<TenantSetupResult | void | undefined> => {
if (process.env.FLUID_TENANT_SETUP_PKG_SPECIFIER !== undefined) {
if (setup) {
// We expect that the specified package provides a setupTenants function.
const { setupTenants } = await import(process.env.FLUID_TENANT_SETUP_PKG_SPECIFIER);
assert(
typeof setupTenants === "function",
"A setupTenants function was not provided from the specified package",
);
assert("waitTime" in args, "waitTime must be provided for tenant setup");
return setupTenants(args) as Promise<TenantSetupResult>;
} else {
// We expect that the specified package provides a releaseTenants function.
const { releaseTenants } = await import(process.env.FLUID_TENANT_SETUP_PKG_SPECIFIER);
assert(
typeof releaseTenants === "function",
"A releaseTenants function was not provided from the specified package",
);
assert("reservationId" in args, "reservationId must be provided for tenant cleanup");
return releaseTenants(args) as Promise<void>;
}
}
return undefined;
};

/**
* Get from the env a set of credentials to use from a single tenant
* @param tenantIndex - integer to choose the tenant from array of options (if multiple tenants are available)
* @param requestedUserName - specific user name to filter to
* @internal
*/
export function getOdspCredentials(
export async function getOdspCredentials(
odspEndpointName: OdspEndpoint,
tenantIndex: number,
requestedUserName?: string,
): { username: string; password: string }[] {
const creds: { username: string; password: string }[] = [];
const loginTenants =
odspEndpointName === "odsp"
? process.env.login__odsp__test__tenants
: process.env.login__odspdf__test__tenants;

if (loginTenants !== undefined) {
/**
* Parse login credentials using the new tenant format for e2e tests.
* For the expected format of loginTenants, see {@link UserPassCredentials}
*/
if (loginTenants.includes("UserPrincipalName")) {
const output: UserPassCredentials[] = JSON.parse(loginTenants);
if (output?.[tenantIndex] === undefined) {
throw new Error("No resources found in the login tenants");
}

// Return the set of accounts to choose from a single tenant
for (const account of output) {
const username = account.UserPrincipalName;
const password = account.Password;
if (requestedUserName === undefined || requestedUserName === username) {
creds.push({ username, password });
}
}
} else {
/**
* Parse login credentials using the tenant format for stress tests.
* For the expected format of loginTenants, see {@link LoginTenants}
*/
const tenants: LoginTenants = JSON.parse(loginTenants);
const tenantNames = Object.keys(tenants);
const tenant = tenantNames[tenantIndex % tenantNames.length];
if (tenant === undefined) {
throw new Error("tenant should not be undefined when getting odsp credentials");
}
const tenantInfo = tenants[tenant];
if (tenantInfo === undefined) {
throw new Error("tenantInfo should not be undefined when getting odsp credentials");
}
// Translate all the user from that user to the full user principal name by appending the tenant domain
const range = tenantInfo.range;

// Return the set of account to choose from a single tenant
for (let i = 0; i < range.count; i++) {
const username = `${range.prefix}${range.start + i}@${tenant}`;
if (requestedUserName === undefined || requestedUserName === username) {
creds.push({ username, password: range.password });
}
}
}
} else {
const loginAccounts =
odspEndpointName === "odsp"
? process.env.login__odsp__test__accounts
: process.env.login__odspdf__test__accounts;
if (loginAccounts === undefined) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const inCi = !!process.env.TF_BUILD;
const odspOrOdspdf = odspEndpointName === "odsp" ? "odsp" : "odspdf";
assert.fail(
`Missing secrets from environment. At least one of login__${odspOrOdspdf}__test__tenants or login__${odspOrOdspdf}__test__accounts must be set.${
inCi ? "" : "\n\nRun getkeys to populate these environment variables."
}`,
);
}

// Expected format of login__odsp__test__accounts is simply string key-value pairs of username and password
const passwords: { [user: string]: string } = JSON.parse(loginAccounts);

// Need to choose one out of the set as these account might be from different tenant
const username = requestedUserName ?? Object.keys(passwords)[0];
if (username === undefined) {
throw new Error("username should not be undefined when getting odsp credentials");
}
const userPass = passwords[username];
if (userPass === undefined) {
): Promise<TenantSetupResult> {
const creds: UserPassCredentials[] = [];
const odspEndpoint = odspEndpointName === "odsp" ? "prod" : "dogfood";

const result = await importTenantManagerPackage(
{
waitTime: 3600,
accessToken: process.env.SYSTEM_ACCESSTOKEN,
odspEndpoint,
},
true,
);
assert(result !== undefined, "Tenant setup result is undefined.");
const { userPass, reservationId, appClientId } = result;

// Return the set of accounts to choose from a single tenant
for (const account of userPass) {
const { username, password } = account;
if (username === undefined || password === undefined) {
throw new Error(
"password for username should not be undefined when getting odsp credentials",
`username or password should not be undefined when getting odsp credentials - user: ${username}, pass: ${password}`,
);
}
creds.push({ username, password: userPass });
if (requestedUserName === undefined || requestedUserName === username) {
creds.push({ username, password });
}
}
return creds;

const tenantSetupResult: TenantSetupResult = {
userPass: creds,
reservationId,
appClientId,
};
return tenantSetupResult;

// const loginTenants =
// odspEndpointName === "odsp"
// ? process.env.login__odsp__test__tenants
// : process.env.login__odspdf__test__tenants;

// if (loginTenants !== undefined) {
// /**
// * Parse login credentials using the new tenant format for e2e tests.
// * For the expected format of loginTenants, see {@link UserPassCredentials}
// */
// if (loginTenants.includes("UserPrincipalName")) {
// const output: UserPassCredentials[] = JSON.parse(loginTenants);
// if (output?.[tenantIndex] === undefined) {
// throw new Error("No resources found in the login tenants");
// }
// } else {
// /**
// * Parse login credentials using the tenant format for stress tests.
// * For the expected format of loginTenants, see {@link LoginTenants}
// */
// const tenants: LoginTenants = JSON.parse(loginTenants);
// const tenantNames = Object.keys(tenants);
// const tenant = tenantNames[tenantIndex % tenantNames.length];
// if (tenant === undefined) {
// throw new Error("tenant should not be undefined when getting odsp credentials");
// }
// const tenantInfo = tenants[tenant];
// if (tenantInfo === undefined) {
// throw new Error("tenantInfo should not be undefined when getting odsp credentials");
// }
// // Translate all the user from that user to the full user principal name by appending the tenant domain
// const range = tenantInfo.range;

// // Return the set of account to choose from a single tenant
// for (let i = 0; i < range.count; i++) {
// const username = `${range.prefix}${range.start + i}@${tenant}`;
// if (requestedUserName === undefined || requestedUserName === username) {
// creds.push({ username, password: range.password });
// }
// }
// }
// } else {
// const loginAccounts =
// odspEndpointName === "odsp"
// ? process.env.login__odsp__test__accounts
// : process.env.login__odspdf__test__accounts;
// if (loginAccounts === undefined) {
// // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
// const inCi = !!process.env.TF_BUILD;
// const odspOrOdspdf = odspEndpointName === "odsp" ? "odsp" : "odspdf";
// assert.fail(
// `Missing secrets from environment. At least one of login__${odspOrOdspdf}__test__tenants or login__${odspOrOdspdf}__test__accounts must be set.${
// inCi ? "" : "\n\nRun getkeys to populate these environment variables."
// }`,
// );
// }

// // Expected format of login__odsp__test__accounts is simply string key-value pairs of username and password
// const passwords: { [user: string]: string } = JSON.parse(loginAccounts);

// // Need to choose one out of the set as these account might be from different tenant
// const username = requestedUserName ?? Object.keys(passwords)[0];
// if (username === undefined) {
// throw new Error("username should not be undefined when getting odsp credentials");
// }
// const userPass = passwords[username];
// if (userPass === undefined) {
// throw new Error(
// "password for username should not be undefined when getting odsp credentials",
// );
// }
// creds.push({ username, password: userPass });
// }
// return creds;
}

/**
Expand All @@ -199,8 +272,16 @@ export class OdspTestDriver implements ITestDriver {
try {
return await getDriveId(siteUrl, "", undefined, {
accessToken: await this.getStorageToken({ siteUrl, refresh: false }, tokenConfig),
refreshTokenFn: async () =>
this.getStorageToken({ siteUrl, refresh: true }, tokenConfig),
refreshTokenFn: async () => {
await importTenantManagerPackage(
{
reservationId: tokenConfig.reservationId,
odspEndpoint: tokenConfig.endpointName === "odsp" ? "prod" : "dogfood",
},
false,
);
return this.getStorageToken({ siteUrl, refresh: true }, tokenConfig);
},
});
} catch (ex) {
if (tokenConfig.supportsBrowserAuth !== true) {
Expand Down Expand Up @@ -231,7 +312,12 @@ export class OdspTestDriver implements ITestDriver {
const tenantIndex = config?.tenantIndex ?? 0;
assertOdspEndpoint(config?.odspEndpointName);
const endpointName = config?.odspEndpointName ?? "odsp";
const creds = getOdspCredentials(endpointName, tenantIndex, config?.username);
// DEAL WITH THIS
const {
reservationId,
appClientId,
userPass: creds,
} = await getOdspCredentials(endpointName, tenantIndex, config?.username);
// Pick a random one on the list (only supported for >= 0.46)
const randomUserIndex =
compare(api.version, "0.46.0") >= 0
Expand Down Expand Up @@ -273,6 +359,8 @@ export class OdspTestDriver implements ITestDriver {
tenantName,
userIndex,
endpointName,
reservationId,
appClientId,
);
}

Expand Down Expand Up @@ -300,10 +388,14 @@ export class OdspTestDriver implements ITestDriver {
tenantName?: string,
userIndex?: number,
endpointName?: string,
reservationId?: string,
appClientId?: string,
) {
const tokenConfig: TokenConfig = {
...loginConfig,
...getMicrosoftConfiguration(),
clientId: appClientId ?? getMicrosoftConfiguration().clientId,
endpointName,
reservationId,
};

const driveId = await this.getDriveId(loginConfig.siteUrl, tokenConfig);
Expand Down Expand Up @@ -354,10 +446,20 @@ export class OdspTestDriver implements ITestDriver {
}
// This function can handle token request for any multiple sites.
// Where the test driver is for a specific site.
const result = await importTenantManagerPackage(
{
waitTime: 3600,
accessToken: process.env.SYSTEM_ACCESSTOKEN,
odspEndpoint: config.endpointName === "odsp" ? "prod" : "dogfood",
},
true,
);
assert(result !== undefined, "Tenant setup result is undefined.");
const { userPass, reservationId } = result;
const tokens = await this.odspTokenManager.getOdspTokens(
host,
config,
passwordTokenConfig(config.username, config.password),
{ reservationId, ...config },
passwordTokenConfig(userPass[0]?.username, userPass[0]?.password),
options.refresh,
);
return tokens.accessToken;
Expand Down
Loading
Loading