Write tests for SObject and move Callback back to connection.d.ts

This commit is contained in:
Abraham White
2018-08-09 17:28:37 -06:00
parent 692c933823
commit bc74bacf34
7 changed files with 350 additions and 79 deletions

View File

@@ -10,8 +10,16 @@ import { Metadata } from './api/metadata';
import { Bulk } from './bulk';
import { Cache } from './cache'
import { OAuth2, Streaming } from '.';
import { HttpApiOptions } from './http-api'
export type Callback<T> = (err: Error, result: T) => void;
export type Callback<T> = (err: Error | null, result: T) => void;
// The type for these options was determined by looking at the usage
// of the options object in Connection.create and other methods
// go to http://jsforce.github.io/jsforce/doc/connection.js.html#line568
// and search for options
export interface RestApiOptions {
headers?: { [x: string]: string }
}
// These are pulled out because according to http://jsforce.github.io/jsforce/doc/connection.js.html#line49
// the oauth options can either be in the `oauth2` proeprty OR spread across the main connection
@@ -93,24 +101,24 @@ export type ConnectionEvent = "refresh";
*/
export abstract class BaseConnection extends EventEmitter {
_baseUrl(): string;
request(info: RequestInfo | string, options?: Object, callback?: (err: Error, Object: object) => void): Promise<Object>;
request(info: RequestInfo | string, options?: HttpApiOptions, callback?: (err: Error, Object: object) => void): Promise<Object>;
query<T>(soql: string, options?: ExecuteOptions, callback?: (err: Error, result: QueryResult<T>) => void): Query<QueryResult<T>>;
queryMore<T>(locator: string, options?: ExecuteOptions, callback?: (err: Error, result: QueryResult<T>) => void): Promise<QueryResult<T>>;
create<T>(type: string, records: Record<T> | Array<Record<T>>, options?: Object,
create<T>(type: string, records: Record<T> | Array<Record<T>>, options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
insert<T>(type: string, records: Record<T> | Array<Record<T>>, options?: Object,
insert<T>(type: string, records: Record<T> | Array<Record<T>>, options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
retrieve<T>(type: string, ids: string | string[], options?: Object,
retrieve<T>(type: string, ids: string | string[], options?: RestApiOptions,
callback?: (err: Error, result: Record<T> | Array<Record<T>>) => void): Promise<(Record<T> | Array<Record<T>>)>;
update<T>(type: string, records: Record<T> | Array<Record<T>>, options?: Object,
update<T>(type: string, records: Record<T> | Array<Record<T>>, options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | Array<Record<T>>) => void): Promise<(RecordResult | RecordResult[])>;
upsert<T>(type: string, records: Record<T> | Array<Record<T>>, extIdField: string, options?: Object,
upsert<T>(type: string, records: Record<T> | Array<Record<T>>, extIdField: string, options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
del<T>(type: string, ids: string | string[], options?: Object,
del<T>(type: string, ids: string | string[], options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
delete<T>(type: string, ids: string | string[], options?: Object,
delete<T>(type: string, ids: string | string[], options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
destroy<T>(type: string, ids: string | string[], options?: Object,
destroy<T>(type: string, ids: string | string[], options?: RestApiOptions,
callback?: (err: Error, result: RecordResult | RecordResult[]) => void): Promise<(RecordResult | RecordResult[])>;
describe$: {
/** Returns a value from the cache if it exists, otherwise calls Connection.describe */
@@ -124,7 +132,8 @@ export abstract class BaseConnection extends EventEmitter {
clear(): void;
}
describeGlobal<T>(callback?: (err: Error, result: DescribeGlobalResult) => void): Promise<DescribeGlobalResult>;
sobject<T>(resource: string): SObject<T>;
// we want any object to be accepted if the user doesn't decide to give an explicit type
sobject<T = object>(resource: string): SObject<T>;
}
export class Connection extends BaseConnection {

View File

@@ -1 +0,0 @@
export type Callback<T> = (err: Error | null, ret: T) => void;

5
types/jsforce/http-api.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export interface HttpApiOptions {
responseType?: string;
transport?: object;
noContentResponse?: object
}

View File

@@ -6,7 +6,7 @@
// Tim Noonan <https://github.com/tnoonan-salesforce>
// Abraham White <https://github.com/whiteabelincoln>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2
// TypeScript Version: 2.3
export * from './api/analytics';
export * from './api/chatter';

View File

@@ -4,12 +4,9 @@ import * as express from 'express';
import * as glob from 'glob';
import * as sf from 'jsforce';
export interface DummyRecord {
thing: boolean;
other: number;
person: string;
}
import { RecordReference, Record } from 'jsforce/record';
import { SObject } from 'jsforce/salesforce-object';
import { RecordResult } from 'jsforce/record-result';
const salesforceConnection: sf.Connection = new sf.Connection({
instanceUrl: '',
@@ -20,7 +17,304 @@ const salesforceConnection: sf.Connection = new sf.Connection({
},
});
salesforceConnection.sobject<DummyRecord>("Dummy").select(["thing", "other"]);
async function testSObject(connection: sf.Connection) {
interface DummyRecord {
thing: boolean;
other: number;
person: string;
}
const dummySObject: SObject<DummyRecord> = connection.sobject<DummyRecord>('Dummy');
// currently untyped, but some future change may make this stricter
const restApiOptions = {
headers: { Bearer: 'I have no idea what this wants' }
};
{ // Test SObject.record
// $ExpectType RecordReference<DummyRecord>
dummySObject.record('50130000000014C');
}
{ // Test SObject.retrieve
// with single id
// $ExpectType Record<DummyRecord>
await dummySObject.retrieve('50130000000014C');
// with single id and rest api options
// $ExpectType Record<DummyRecord>
await dummySObject.retrieve('50130000000014C', restApiOptions);
// with single id and callback
dummySObject.retrieve('50130000000014C', restApiOptions, (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType Record<DummyRecord>
});
// with ids array
// $ExpectType Record<DummyRecord>[]
await dummySObject.retrieve(['IIIIDDD']);
// with ids array and rest api options
// $ExpectType Record<DummyRecord>[]
await dummySObject.retrieve(['IIIIDDD'], restApiOptions);
// with ids array and callback
dummySObject.retrieve(['50130000000014C'], restApiOptions, (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType Record<DummyRecord>[]
});
salesforceConnection.sobject<any>("ContentVersion").retrieve("world", {
test: "test"
}, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType any
});
}
{ // Test SObject.update
// if we require that records have an id field this will fail
// //$ExpectError
await dummySObject.update({ thing: false });
// If we require that the records have an Id field
// await dummySObject.update({ thing: false, Id: 'asdf' }); // $ExpectType RecordResult
// invalid field
// $ExpectError
await dummySObject.update({ asdf: false });
// with rest api options
// $ExpectType RecordResult
await dummySObject.update({ thing: false }, restApiOptions);
// with callback
dummySObject.update({ thing: false }, (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType RecordResult
});
dummySObject.update({ thing: false }, restApiOptions, (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType RecordResult
});
// with multiple records
// $ExpectType RecordResult[]
await dummySObject.update([{ thing: false }]);
// with multiple records and api options
// $ExpectType RecordResult[]
await dummySObject.update([{ thing: false }], restApiOptions);
// with multiple records and callback
dummySObject.update([{ thing: false }], restApiOptions, (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType RecordResult[]
});
dummySObject.update([{ thing: false }], (err, res) => {
err; // $ExpectType Error | null
res; // $ExpectType RecordResult[]
});
}
{ // Test SObject.updated
// $ExpectType UpdatedRecordsInfo
await dummySObject.updated(new Date(), new Date());
// $ExpectType UpdatedRecordsInfo
await dummySObject.updated(new Date(), 'hi');
// $ExpectType UpdatedRecordsInfo
await dummySObject.updated('hi', new Date());
// $ExpectType UpdatedRecordsInfo
await dummySObject.updated('hi', 'hi');
dummySObject.updated(new Date(), 'hi', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType UpdatedRecordsInfo
});
}
{ // Test SObject.upsert
const updateData = {
Id: 'Some ID',
thing: true,
other: 1,
person: 'hi'
};
// $ExpectType RecordResult
await dummySObject.upsert(updateData, 'Id');
// $ExpectType RecordResult
await dummySObject.upsert(updateData, 'Id', restApiOptions);
// $ExpectType RecordResult[]
await dummySObject.upsert([updateData], 'Id');
// $ExpectType RecordResult[]
await dummySObject.upsert([updateData], 'Id', restApiOptions);
dummySObject.upsert(updateData, 'Id', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
dummySObject.upsert(updateData, 'Id', restApiOptions, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
dummySObject.upsert([updateData], 'Id', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
dummySObject.upsert([updateData], 'Id', restApiOptions, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
}
{ // Test SObject.find
}
{ // Test SObject.findOne
salesforceConnection.sobject("ContentVersion").findOne<any>({ Id: '' }, (err, contentVersion) => {
err; // $ExpectType Error | null
contentVersion; // $ExpectType any
});
}
{ // Test SObject.select
dummySObject.select(["thing", "other"]);
// note the following should never compile:
// $ExpectError
dummySObject.select(["lol"]);
}
{ // Test SObject.create
// $ExpectType RecordResult
await dummySObject.create({
thing: true,
other: 1,
person: 'hi'
});
// $ExpectType RecordResult
await dummySObject.create({
thing: true,
other: 1,
person: 'hi'
}, restApiOptions);
// $ExpectType RecordResult[]
await dummySObject.create([{
thing: true,
other: 1,
person: 'hi'
}]);
// $ExpectType RecordResult[]
await dummySObject.create([{
thing: true,
other: 1,
person: 'hi'
}], restApiOptions);
dummySObject.create([{
thing: true,
other: 1,
person: 'hi'
}], (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
dummySObject.create([{
thing: true,
other: 1,
person: 'hi'
}], restApiOptions, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
salesforceConnection.sobject("Account").create({
Name: "Test Acc 2",
BillingStreet: "Maplestory street",
BillingPostalCode: "ME4 666"
}, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
// callback and rest api options
salesforceConnection.sobject("ContentVersion").create({
OwnerId: '',
Title: 'hello',
PathOnClient: './hello-world.jpg',
VersionData: '{ Test: Data }'
}, restApiOptions, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
salesforceConnection.sobject("ContentDocumentLink").create({
ContentDocumentId: '',
LinkedEntityId: '',
ShareType: "I"
}, (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
}
{ // Test SObject.destroy and aliases
// $ExpectType RecordResult
await dummySObject.del('Id');
// $ExpectType RecordResult
await dummySObject.destroy('Id');
// $ExpectType RecordResult
await dummySObject.delete('Id');
// $ExpectType RecordResult[]
await dummySObject.del(['Id']);
// $ExpectType RecordResult[]
await dummySObject.destroy(['Id']);
// $ExpectType RecordResult[]
await dummySObject.delete(['Id']);
dummySObject.del('Id', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
dummySObject.destroy('Id', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
dummySObject.delete('Id', (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult
});
dummySObject.del(['Id'], (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
dummySObject.destroy(['Id'], (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
dummySObject.delete(['Id'], (err, ret) => {
err; // $ExpectType Error | null
ret; // $ExpectType RecordResult[]
});
}
}
const requestInfo: sf.RequestInfo = {
body: '',
@@ -38,51 +332,6 @@ const queryOptions: sf.ExecuteOptions = {
};
salesforceConnection.query('SELECT Id, Name FROM Account', queryOptions);
// note the following should never compile:
// salesforceConnection.sobject<DummyRecord>("Dummy").select(["lol"]);
salesforceConnection.sobject("Account").create({
Name: "Test Acc 2",
BillingStreet: "Maplestory street",
BillingPostalCode: "ME4 666"
}, (err: Error, ret: sf.RecordResult | sf.RecordResult[]) => {
if (err || !Array.isArray(ret) && !ret.success) {
return;
}
});
salesforceConnection.sobject("ContentVersion").create({
OwnerId: '',
Title: 'hello',
PathOnClient: './hello-world.jpg',
VersionData: '{ Test: Data }'
}, (err: Error, ret: sf.RecordResult | sf.RecordResult[]) => {
if (err || !Array.isArray(ret) && !ret.success) {
return;
}
});
salesforceConnection.sobject<any>("ContentVersion").retrieve("world", {
test: "test"
}, (err: Error, ret) => {
if (err) {
return;
}
});
salesforceConnection.sobject("ContentVersion").findOne<any>({ Id: '' }, (err, contentVersion) => {
});
salesforceConnection.sobject("ContentDocumentLink").create({
ContentDocumentId: '',
LinkedEntityId: '',
ShareType: "I"
}, (err: Error, ret: sf.RecordResult | sf.RecordResult[]) => {
if (err || !Array.isArray(ret) && !ret.success) {
return;
}
});
sf.Date.YESTERDAY;
salesforceConnection.sobject<any>('Coverage__c')
@@ -230,7 +479,7 @@ async function testChatter(conn: sf.Connection): Promise<void> {
},
feedElementType: 'FeedItem',
subjectId: 'me'
}, (err: Error, result: any) => {
}, (err: Error | null, result: any) => {
if (err) {
throw err;
}
@@ -242,7 +491,7 @@ async function testChatter(conn: sf.Connection): Promise<void> {
text: 'This is new comment on the post'
}]
}
}, (err: Error, result: any) => {
}, (err: Error | null, result: any) => {
if (err) {
throw err;
}
@@ -366,13 +615,15 @@ async function testDescribe() {
object.fields.forEach(field => {
const type: sf.FieldType = field.type;
// following should never compile
// const fail = type === 'hey'
// $ExpectError
const fail = type === 'hey';
const isString = type === 'string';
});
// following should never compile (if StrictNullChecks is on)
// object.keyPrefix.length;
// $ExpectError
object.keyPrefix.length;
console.log(`${sobject.name} Label: `, object.label);

View File

@@ -1,4 +1,4 @@
import { Callback } from './global';
import { Callback } from './connection';
import { Record } from './record';
export class QuickAction {

View File

@@ -5,22 +5,29 @@ import { DescribeSObjectResult } from './describe-result';
import { Query } from './query';
import { Record, RecordReference } from './record';
import { RecordResult } from './record-result';
import { Connection, Callback } from './connection';
import { Connection, RestApiOptions, Callback } from './connection';
import { SalesforceId } from './salesforce-id';
import { Batch, BatchResultInfo } from './batch';
import { QuickAction, QuickActionInfo } from './quick-action';
export class SObject<T> {
record(id: SalesforceId): RecordReference<T>;
retrieve(id: SalesforceId, options?: Object, callback?: Callback<Record<T>>): Promise<Record<T>>;
retrieve(ids: SalesforceId[], options?: Object, callback?: Callback<Array<Record<T>>>): Promise<Array<Record<T>>>;
update(record: Partial<T>, options?: Object, callback?: Callback<RecordResult>): Promise<RecordResult>;
update(records: Array<Partial<T>>, options?: Object, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
retrieve(id: SalesforceId, callback?: Callback<Record<T>>): Promise<Record<T>>;
retrieve(id: SalesforceId, options?: object, callback?: Callback<Record<T>>): Promise<Record<T>>;
retrieve(ids: SalesforceId[], callback?: Callback<Array<Record<T>>>): Promise<Array<Record<T>>>;
retrieve(ids: SalesforceId[], options?: object, callback?: Callback<Array<Record<T>>>): Promise<Array<Record<T>>>;
// Should update require that the record Id field be provided?
update(record: Partial<T>, callback?: Callback<RecordResult>): Promise<RecordResult>;
update(record: Partial<T>, options?: RestApiOptions, callback?: Callback<RecordResult>): Promise<RecordResult>;
update(records: Array<Partial<T>>, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
update(records: Array<Partial<T>>, options?: RestApiOptions, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
// FIXME: should input really be optional? the documentation says so, but how can you actually update without it?
updateBulk(input?: Record[] | stream.Stream | string, callback?: Callback<RecordResult[]>): Batch;
updated(start: string | Date, end: string | Date, callback?: Callback<UpdatedRecordsInfo>): Promise<UpdatedRecordsInfo>;
upsert(records: Record<T>, extIdField: SalesforceId, options?: Object, callback?: Callback<RecordResult>): Promise<RecordResult>;
upsert(records: Array<Record<T>>, extIdField: SalesforceId, options?: Object, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
upsert(records: Record<T>, extIdField: string, callback?: Callback<RecordResult>): Promise<RecordResult>;
upsert(records: Record<T>, extIdField: string, options?: RestApiOptions, callback?: Callback<RecordResult>): Promise<RecordResult>;
upsert(records: Array<Record<T>>, extIdField: string, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
upsert(records: Array<Record<T>>, extIdField: string, options?: RestApiOptions, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
upsertBulk(input?: Array<Record<T>> | stream.Stream | string, callback?: Callback<RecordResult[] | BatchResultInfo[]>): Batch;
find<T>(query?: object | string, callback?: Callback<Array<Record<T>>>): Query<Array<Record<T>>>;
@@ -45,9 +52,9 @@ export class SObject<T> {
}
compactLayouts(callback?: Callback<CompactLayoutInfo>): Promise<CompactLayoutInfo>;
count(conditions?: Object | string, callback?: Callback<number>): Query<number>;
create(record: T, options: object, callback?: Callback<RecordResult>): Promise<RecordResult>;
create(record: T, options?: RestApiOptions, callback?: Callback<RecordResult>): Promise<RecordResult>;
create(record: T, callback?: Callback<RecordResult>): Promise<RecordResult>;
create(record: Array<T>, options: object, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
create(record: Array<T>, options?: RestApiOptions, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
create(record: Array<T>, callback?: Callback<RecordResult[]>): Promise<RecordResult[]>;
// FIXME: why does the callback return a single RecordResult instead of an array as in the documentation?
createBulk(input?: Array<Record<T>> | stream.Stream | string, callback?: Callback<RecordResult>): Batch;