Skip to content

Commit

Permalink
Added processActivityDirect method in CloudAdapter (microsoft#4380)
Browse files Browse the repository at this point in the history
Co-authored-by: Emiliano Quiroga <[email protected]>
Co-authored-by: CeciliaAvila <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2023
1 parent 6583ddc commit 5e5d819
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 4 deletions.
2 changes: 2 additions & 0 deletions libraries/botbuilder/etc/botbuilder.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ActivityHandlerBase } from 'botbuilder-core';
import { AppBasedLinkQuery } from 'botbuilder-core';
import { AppCredentials } from 'botframework-connector';
import { AttachmentData } from 'botbuilder-core';
import { AuthenticateRequestResult } from 'botframework-connector';
import { AuthenticationConfiguration } from 'botframework-connector';
import { BatchFailedEntriesResponse } from 'botbuilder-core';
import { BatchOperationResponse } from 'botbuilder-core';
Expand Down Expand Up @@ -244,6 +245,7 @@ export class CloudAdapter extends CloudAdapterBase implements BotFrameworkHttpAd
connectNamedPipe(pipeName: string, logic: (context: TurnContext) => Promise<void>, appId: string, audience: string, callerId?: string, retryCount?: number): Promise<void>;
process(req: Request_2, res: Response_2, logic: (context: TurnContext) => Promise<void>): Promise<void>;
process(req: Request_2, socket: INodeSocket, head: INodeBuffer, logic: (context: TurnContext) => Promise<void>): Promise<void>;
processActivityDirect(authorization: string | AuthenticateRequestResult, activity: Activity, logic: (context: TurnContext) => Promise<void>): Promise<void>;
}

// Warning: (ae-forgotten-export) The symbol "CloudChannelServiceHandler" needs to be exported by the entry point index.d.ts
Expand Down
2 changes: 2 additions & 0 deletions libraries/botbuilder/src/botFrameworkAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,8 @@ export class BotFrameworkAdapter
/**
* Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity.
*
* Use [CloudAdapter.processActivityDirect] instead.
*
* @param activity The activity to process.
* @param logic The function to call at the end of the middleware pipeline.
*
Expand Down
23 changes: 22 additions & 1 deletion libraries/botbuilder/src/cloudAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export class CloudAdapter extends CloudAdapterBase implements BotFrameworkHttpAd
}

const authHeader = z.string().parse(req.headers.Authorization ?? req.headers.authorization ?? '');

try {
const invokeResponse = await this.processActivity(authHeader, activity, logic);
return end(invokeResponse?.status ?? StatusCodes.OK, invokeResponse?.body);
Expand All @@ -146,6 +145,28 @@ export class CloudAdapter extends CloudAdapterBase implements BotFrameworkHttpAd
}
}

/**
* Asynchronously process an activity running the provided logic function.
*
* @param authorization The authorization header in the format: "Bearer [longString]" or the AuthenticateRequestResult for this turn.
* @param activity The activity to process.
* @param logic The logic function to apply.
* @returns a promise representing the asynchronous operation.
*/
async processActivityDirect(
authorization: string | AuthenticateRequestResult,
activity: Activity,
logic: (context: TurnContext) => Promise<void>
): Promise<void> {
try {
typeof authorization === 'string'
? await this.processActivity(authorization, activity, logic)
: await this.processActivity(authorization, activity, logic);
} catch (err) {
throw new Error(`CloudAdapter.processActivityDirect(): ERROR\n ${err.stack}`);
}
}

/**
* Used to connect the adapter to a named pipe.
*
Expand Down
106 changes: 103 additions & 3 deletions libraries/botbuilder/tests/cloudAdapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ const { expect } = require('chai');
const sinon = require('sinon');
const {
AuthenticationConfiguration,
AuthenticationConstants,
BotFrameworkAuthenticationFactory,
allowedCallersClaimsValidator,
} = require('botframework-connector');
const {
CloudAdapter,
ConfigurationBotFrameworkAuthentication,
ConfigurationServiceClientCredentialFactory,
ActivityTypes,
createBotFrameworkAuthenticationFromConfiguration,
INVOKE_RESPONSE_KEY,
} = require('..');
const { NamedPipeServer } = require('botframework-streaming');
const { StatusCodes } = require('botframework-schema');
const { CallerIdConstants } = require('../../botbuilder-core/lib/index');

const FakeBuffer = () => Buffer.from([]);
const FakeNodeSocket = () => new net.Socket();
Expand Down Expand Up @@ -54,6 +57,34 @@ describe('CloudAdapter', function () {
});

describe('process', function () {
class TestConfiguration {
static DefaultConfig = {
// [AuthenticationConstants.ChannelService]: undefined,
ValidateAuthority: true,
ToChannelFromBotLoginUrl: AuthenticationConstants.ToChannelFromBotLoginUrl,
ToChannelFromBotOAuthScope: AuthenticationConstants.ToChannelFromBotOAuthScope,
ToBotFromChannelTokenIssuer: AuthenticationConstants.ToBotFromChannelTokenIssuer,
ToBotFromEmulatorOpenIdMetadataUrl: AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl,
CallerId: CallerIdConstants.PublicAzureChannel,
ToBotFromChannelOpenIdMetadataUrl: AuthenticationConstants.ToBotFromChannelOpenIdMetadataUrl,
OAuthUrl: AuthenticationConstants.OAuthUrl,
// [AuthenticationConstants.OAuthUrlKey]: 'test',
[AuthenticationConstants.BotOpenIdMetadataKey]: null,
};

constructor(config = {}) {
this.configuration = Object.assign({}, TestConfiguration.DefaultConfig, config);
}

get(_path) {
return this.configuration;
}

set(_path, _val) {}
}

const activity = { type: ActivityTypes.Invoke, value: 'invoke' };
const authorization = 'Bearer Authorization';
it('delegates to connect', async function () {
const req = {};
const socket = FakeNodeSocket();
Expand All @@ -70,9 +101,6 @@ describe('CloudAdapter', function () {
});

it('delegates to processActivity', async function () {
const authorization = 'Bearer Authorization';
const activity = { type: ActivityTypes.Invoke, value: 'invoke' };

const req = httpMocks.createRequest({
method: 'POST',
headers: { authorization },
Expand Down Expand Up @@ -152,6 +180,78 @@ describe('CloudAdapter', function () {
assert.equal(StatusCodes.UNAUTHORIZED, res.statusCode);
expect(consoleStub.calledWithMatch({ message: 'The token has expired' })).to.be.true;
});

it('calls processActivityDirect with string authorization', async function () {
const logic = async (context) => {
context.turnState.set(INVOKE_RESPONSE_KEY, {
type: ActivityTypes.InvokeResponse,
value: {
status: 200,
body: 'invokeResponse',
},
});
};

const mock = sandbox.mock(adapter);
mock.expects('processActivity').withArgs(authorization, activity, logic).once().resolves();
mock.expects('connect').never();

await adapter.processActivityDirect(authorization, activity, logic);

mock.verify();
});

it('calls processActivityDirect with AuthenticateRequestResult authorization', async function () {
const claimsIdentity = adapter.createClaimsIdentity('appId');
const audience = AuthenticationConstants.ToChannelFromBotOAuthScope;
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(
TestConfiguration.DefaultConfig
);
const connectorFactory = botFrameworkAuthentication.createConnectorFactory(claimsIdentity);
//AuthenticateRequestResult
const authentication = {
audience: audience,
claimsIdentity: claimsIdentity,
callerId: 'callerdId',
connectorFactory: connectorFactory,
};
const logic = async (context) => {
context.turnState.set(INVOKE_RESPONSE_KEY, {
type: ActivityTypes.InvokeResponse,
value: {
status: 200,
body: 'invokeResponse',
},
});
};

const mock = sandbox.mock(adapter);
mock.expects('processActivity').withArgs(authentication, activity, logic).once().resolves();
mock.expects('connect').never();

await adapter.processActivityDirect(authentication, activity, logic);

mock.verify();
});

it('calls processActivityDirect with error', async function () {
const logic = async (context) => {
context.turnState.set(INVOKE_RESPONSE_KEY, {
type: ActivityTypes.InvokeResponse,
value: {
status: 200,
body: 'invokeResponse',
},
});
};

sandbox.stub(adapter, 'processActivity').throws({ stack: 'error stack' });

await assert.rejects(
adapter.processActivityDirect(authorization, activity, logic),
new Error('CloudAdapter.processActivityDirect(): ERROR\n error stack')
);
});
});

describe('connectNamedPipe', function () {
Expand Down

0 comments on commit 5e5d819

Please sign in to comment.