892 lines
26 KiB
ObjectPascal
892 lines
26 KiB
ObjectPascal
/// regression tests for mORMot's cross-platform units
|
|
// - this unit is a part of the freeware Synopse mORMot framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynCrossPlatformTests;
|
|
|
|
{
|
|
This file is part of Synopse mORMot framework.
|
|
|
|
Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://synopse.info
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (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.mozilla.org/MPL
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is Synopse mORMot framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
Should compile with Delphi for any platform, or with FPC or Kylix
|
|
|
|
}
|
|
|
|
{$i SynCrossPlatform.inc} // define e.g. HASINLINE
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils,
|
|
Classes,
|
|
Variants,
|
|
TypInfo,
|
|
{$ifdef ISDELPHI2010}
|
|
System.Generics.Collections,
|
|
{$endif}
|
|
{$ifndef NEXTGEN}
|
|
Contnrs,
|
|
{$endif}
|
|
mORMotClient, // as generated by mORMotWrappers.pas !
|
|
SynCrossPlatformJSON,
|
|
SynCrossPlatformCrypto,
|
|
SynCrossPlatformSpecific,
|
|
SynCrossPlatformRest;
|
|
|
|
|
|
type
|
|
/// the prototype of an individual test
|
|
// - to be used with TSynTest descendants
|
|
TSynTestEvent = procedure of object;
|
|
|
|
{$M+} { we need the RTTI for the published methods of this object class }
|
|
/// generic class for performing simple tests
|
|
// - purpose of this ancestor is to have RTTI for its published methods,
|
|
// which will contain the tests
|
|
TSynTest = class
|
|
protected
|
|
fFailureMsg: string;
|
|
fCurrentTest: Integer;
|
|
public
|
|
/// the test case name
|
|
Ident: string;
|
|
/// the registered tests, i.e. all published methods of this class
|
|
Tests: TPublishedMethodDynArray;
|
|
/// how many Check() call did pass
|
|
Passed: cardinal;
|
|
/// how many Check() call did failed
|
|
Failed: cardinal;
|
|
/// create the test instance
|
|
// - this constructor will add all published methods to the internal
|
|
// test list, accessible via the Count/TestName/TestMethod properties
|
|
constructor Create(const aIdent: string='');
|
|
/// run all tests
|
|
procedure Run(LogToConsole: boolean);
|
|
/// validate a test
|
|
procedure Check(test: Boolean; const Msg: string=''); overload;
|
|
published
|
|
end;
|
|
|
|
/// regression tests of our CrossPlatform units
|
|
TSynCrossPlatformTests = class(TSynTest)
|
|
published
|
|
procedure Iso8601DateTime;
|
|
procedure Base64Encoding;
|
|
procedure JSON;
|
|
procedure Model;
|
|
procedure Cryptography;
|
|
end;
|
|
|
|
/// regression tests of our CrossPlatform units
|
|
TSynCrossPlatformClient = class(TSynTest)
|
|
protected
|
|
fAuthentication: TSQLRestServerAuthenticationClass;
|
|
fClient: TSQLRestClientHTTP;
|
|
public
|
|
constructor Create(aAuthentication: TSQLRestServerAuthenticationClass); reintroduce;
|
|
destructor Destroy; override;
|
|
published
|
|
procedure Connection;
|
|
procedure ORM;
|
|
procedure ORMBatch;
|
|
procedure Services;
|
|
procedure CleanUp;
|
|
end;
|
|
{$M-}
|
|
|
|
|
|
implementation
|
|
|
|
type
|
|
TSQLRecordPeopleSimple = class(TSQLRecord)
|
|
private
|
|
fData: TSQLRawBlob;
|
|
fFirstName: RawUTF8;
|
|
fLastName: RawUTF8;
|
|
fYearOfBirth: integer;
|
|
fYearOfDeath: word;
|
|
published
|
|
property FirstName: RawUTF8 read fFirstName write fFirstName;
|
|
property LastName: RawUTF8 read fLastName write fLastName;
|
|
property Data: TSQLRawBlob read fData write fData;
|
|
property YearOfBirth: integer read fYearOfBirth write fYearOfBirth;
|
|
property YearOfDeath: word read fYearOfDeath write fYearOfDeath;
|
|
end;
|
|
|
|
TMainNested = class(TCollectionItem)
|
|
private
|
|
fNumber: double;
|
|
fIdent: RawUTF8;
|
|
published
|
|
property Ident: RawUTF8 read fIdent write fIdent;
|
|
property Number: double read fNumber write fNumber;
|
|
end;
|
|
|
|
TMain = class(TPersistent)
|
|
private
|
|
fName: RawUTF8;
|
|
fNested: TCollection;
|
|
fList: TStringList;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
published
|
|
property Name: RawUTF8 read fName write fName;
|
|
property Nested: TCollection read fNested;
|
|
property List: TStringList read fList;
|
|
end;
|
|
|
|
|
|
{ TSynTest }
|
|
|
|
procedure TSynTest.Check(test: Boolean; const Msg: string='');
|
|
begin
|
|
if test then
|
|
inc(Passed) else begin
|
|
inc(Failed);
|
|
if Msg<>'' then
|
|
fFailureMsg := fFailureMsg+'['+Msg+'] ';
|
|
end;
|
|
end;
|
|
|
|
constructor TSynTest.Create(const aIdent: string);
|
|
begin
|
|
Ident := aIdent;
|
|
GetPublishedMethods(self,Tests);
|
|
end;
|
|
|
|
procedure TSynTest.Run(LogToConsole: boolean);
|
|
var i: integer;
|
|
BeforePassed,BeforeFailed: cardinal;
|
|
startclass, startmethod: TDateTime;
|
|
datetime: string;
|
|
LogFile: text;
|
|
procedure Log(const Fmt: string; const Args: array of const);
|
|
var msg: string;
|
|
begin
|
|
msg := format(Fmt,Args);
|
|
if LogToConsole then
|
|
writeln(msg) else
|
|
writeln(LogFile,msg);
|
|
if not LogToConsole then
|
|
Flush(LogFile);
|
|
end;
|
|
begin
|
|
startclass := Now;
|
|
datetime := DateTimeToIso8601(startclass);
|
|
if not LogToConsole then begin
|
|
assign(LogFile,ExtractFilePath(ParamStr(0))+
|
|
FormatDateTime('yyyy mm dd hh nn ss',startclass)+'.txt');
|
|
rewrite(LogFile);
|
|
end;
|
|
Log(#13#10' %s'#13#10'%s',[Ident,StringOfChar('-',length(Ident)+2)]);
|
|
for i := 0 to high(Tests) do begin
|
|
Log(#13#10' %d. Running "%s"',[i+1,Tests[i].Name]);
|
|
startmethod := Now;
|
|
BeforePassed := Passed;
|
|
BeforeFailed := Failed;
|
|
try
|
|
fCurrentTest := i;
|
|
TSynTestEvent(Tests[i].Method)();
|
|
except
|
|
on E: Exception do
|
|
Check(False,format('Exception %s raised with message "%s"',[E.ClassName,E.Message]));
|
|
end;
|
|
if Failed<>BeforeFailed then
|
|
Log(' !!! %d test(s) failed / %d %s',[Failed-BeforeFailed,
|
|
Failed-BeforeFailed+Passed-BeforePassed,fFailureMsg]) else
|
|
Log(' %d tests passed in %s',[Passed-BeforePassed,
|
|
FormatDateTime('nn:ss:zzz',Now-startmethod)]);
|
|
fFailureMsg := '';
|
|
end;
|
|
Log(#13#10' Tests failed: %d / %d'#13#10' Time elapsed: %s'#13#10#13#10' %s',
|
|
[Failed,Failed+Passed,FormatDateTime('nn:ss:zzz',Now-startclass),datetime]);
|
|
if not LogToConsole then
|
|
close(LogFile);
|
|
end;
|
|
|
|
|
|
{ TSynCrossPlatformTests }
|
|
|
|
procedure TSynCrossPlatformTests.Base64Encoding;
|
|
var b,c: TByteDynArray;
|
|
i: integer;
|
|
begin
|
|
check(b=nil);
|
|
for i := 0 to 100 do begin
|
|
SetLength(b,i);
|
|
if i>0 then
|
|
b[i-1] := i;
|
|
check(Base64JSONStringToBytes(BytesToBase64JSONString(b),c));
|
|
check(length(c)=i);
|
|
check(CompareMem(Pointer(b),pointer(c),i));
|
|
end;
|
|
end;
|
|
|
|
procedure TSynCrossPlatformTests.Cryptography;
|
|
var c: array of byte;
|
|
s: string;
|
|
begin
|
|
SetLength(c,5);
|
|
c[4] := $96;
|
|
Check(crc32(0,c)=$DF4EC16C,'crc32');
|
|
Check(crc32ascii(0,'abcdefghijklmnop')=$943AC093);
|
|
SetLength(c,3);
|
|
c[0] := ord('a');
|
|
c[1] := ord('b');
|
|
c[2] := ord('c');
|
|
s := SHA256(c);
|
|
check(s='ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
|
|
check(SHA256('abc')=s);
|
|
end;
|
|
|
|
procedure TSynCrossPlatformTests.Iso8601DateTime;
|
|
procedure Test(D: TDateTime);
|
|
var s: string;
|
|
procedure One(D: TDateTime);
|
|
var E: TDateTime;
|
|
V: TTimeLog;
|
|
begin
|
|
s := DateTimeToIso8601(D);
|
|
E := Iso8601ToDateTime(s);
|
|
Check(Abs(D-E)<(1/SecsPerDay)); // we allow 1 sec error
|
|
Check(DateTimeToJSON(D)='"'+s+'"');
|
|
V := DateTimeToTTimeLog(D);
|
|
E := TTimeLogToDateTime(V);
|
|
Check(Abs(D-E)<(1/SecsPerDay));
|
|
Check(UrlDecode(UrlEncode(s))=s);
|
|
end;
|
|
begin
|
|
One(D);
|
|
Check(length(s)=19);
|
|
One(Trunc(D));
|
|
Check(length(s)=10);
|
|
One(Frac(D));
|
|
Check(length(s)=9);
|
|
end;
|
|
var D: TDateTime;
|
|
i: integer;
|
|
s: string;
|
|
T: TTimeLog;
|
|
begin
|
|
s := '2014-06-28T11:50:22';
|
|
D := Iso8601ToDateTime(s);
|
|
Check(Abs(D-41818.49331)<(1/SecsPerDay));
|
|
Check(DateTimeToIso8601(D)=s);
|
|
T := DateTimeToTTimeLog(D);
|
|
Check(T=135181810838);
|
|
D := Now/20+Random*20; // some starting random date/time
|
|
for i := 1 to 2000 do begin
|
|
Test(D);
|
|
D := D+Random*57; // go further a little bit: change date/time
|
|
end;
|
|
end;
|
|
|
|
procedure TSynCrossPlatformTests.JSON;
|
|
var doc: variant;
|
|
js,json2,inlined: string;
|
|
i: integer;
|
|
obj1,obj2: TMain;
|
|
item: TMainNested;
|
|
begin
|
|
doc := JSONVariant('{"test":1234,"name":"Joh\"n\r","zero":0.0}');
|
|
check(doc.test=1234);
|
|
check(doc.name='Joh"n'#13);
|
|
check(doc.name2=null);
|
|
check(doc.zero=0);
|
|
js := doc;
|
|
check(js='{"test":1234,"name":"Joh\"n\r","zero":0}');
|
|
{$ifdef FPC}
|
|
TJSONVariantData(doc)['name2'] := 3.1415926;
|
|
TJSONVariantData(doc)['name'] := 'John';
|
|
{$else}
|
|
doc.name2 := 3.1415926;
|
|
doc.name := 'John';
|
|
{$endif}
|
|
js := doc;
|
|
check(js='{"test":1234,"name":"John","zero":0,"name2":3.1415926}');
|
|
doc := JSONVariant('[{ID:1,"Username":"xx","FirstName":"System",Active:-1}]');
|
|
check(TJSONVariantData(doc).Kind=jvArray);
|
|
check(TJSONVariantData(doc).Count=1);
|
|
check(TJSONVariantData(doc).Values[0].ID=1);
|
|
check(TJSONVariantData(doc).Values[0].Username='xx');
|
|
check(TJSONVariantData(doc).Values[0].Active=-1);
|
|
check(IsRowID('id'));
|
|
check(IsRowID('iD'));
|
|
check(IsRowID('rowid'));
|
|
check(IsRowID('RowID'));
|
|
check(not IsRowID('iz'));
|
|
check(not IsRowID('i2'));
|
|
check(not IsRowID('rawid'));
|
|
check(not IsRowID(''));
|
|
check(FormatBind('',[])='');
|
|
for i := 1 to 1000 do begin
|
|
js := IntToStr(i);
|
|
inlined := ':('+js+'):';
|
|
check(FormatBind(js,[])=js);
|
|
check(FormatBind(js,[i])=js);
|
|
check(FormatBind('?',[i])=inlined);
|
|
check(FormatBind('a?a',[i])='a'+inlined+'a');
|
|
check(FormatBind('a?',[i])='a'+inlined);
|
|
check(FormatBind('?a',[i])=inlined+'a');
|
|
check(FormatBind('ab?',[i])='ab'+inlined);
|
|
check(FormatBind('?ab',[i])=inlined+'ab');
|
|
check(FormatBind('ab?ab',[i])='ab'+inlined+'ab');
|
|
check(FormatBind('abc?abc',[i])='abc'+inlined+'abc');
|
|
check(FormatBind('abc?abc',[i,1])='abc'+inlined+'abc');
|
|
check(FormatBind(js+'?',[i])=js+inlined);
|
|
check(FormatBind('?'+js,[i])=inlined+js);
|
|
check(FormatBind('ab?ab',[js])='ab:("'+js+'"):ab');
|
|
check(FormatBind('ab?ab',[variant(js)])='ab:("'+js+'"):ab');
|
|
check(FormatBind('ab?ab',[variant(i)])='ab'+inlined+'ab');
|
|
check(FormatBind('ab?ab?',[variant(i)])='ab'+inlined+'ab:(null):');
|
|
check(FormatBind('ab?ab??cd',[i,i,js])='ab'+inlined+'ab'+inlined+
|
|
':("'+js+'"):cd');
|
|
end;
|
|
RegisterClassForJSON([TMainNested]); // for JSONToNewObject()
|
|
obj1 := TMain.Create;
|
|
obj2 := TMain.Create;
|
|
try
|
|
for i := 1 to 100 do begin
|
|
obj1.Name := IntToStr(i);
|
|
item := obj1.Nested.Add as TMainNested;
|
|
item.Ident := obj1.Name;
|
|
item.Number := i/2;
|
|
check(obj1.Nested.Count=i);
|
|
obj1.list.Add(obj1.Name);
|
|
js := ObjectToJSON(obj1);
|
|
check(js<>'');
|
|
if i=1 then
|
|
check(js='{"Name":"1","Nested":[{"Ident":"1","Number":0.5}],"List":["1"]}');
|
|
JSONToObject(obj2,js);
|
|
check(obj2.Nested.Count=i);
|
|
json2 := ObjectToJSON(obj2);
|
|
check(json2=js);
|
|
js := ObjectToJSON(item,true);
|
|
item := TMainNested(JSONToNewObject(js));
|
|
check(item<>nil);
|
|
json2 := ObjectToJSON(item,true);
|
|
check(json2=js);
|
|
item.Free;
|
|
end;
|
|
finally
|
|
obj2.Free;
|
|
obj1.Free;
|
|
end;
|
|
js := 'one,two,3';
|
|
i := 1;
|
|
check(GetNextCSV(js,i,json2));
|
|
check(json2='one');
|
|
check(GetNextCSV(js,i,json2));
|
|
check(json2='two');
|
|
check(GetNextCSV(js,i,json2));
|
|
check(json2='3');
|
|
check(not GetNextCSV(js,i,json2));
|
|
check(not GetNextCSV(js,i,json2));
|
|
js := 'one';
|
|
i := 1;
|
|
check(GetNextCSV(js,i,json2));
|
|
check(json2='one');
|
|
check(not GetNextCSV(js,i,json2));
|
|
js := '';
|
|
i := 1;
|
|
check(not GetNextCSV(js,i,json2));
|
|
doc := JsonVariant('{}');
|
|
js := doc;
|
|
check(js='{}');
|
|
end;
|
|
|
|
procedure TSynCrossPlatformTests.Model;
|
|
var mdel: TSQLModel;
|
|
people: TSQLRecordPeopleSimple;
|
|
i: integer;
|
|
js: string;
|
|
fields: TSQLFieldBits;
|
|
begin
|
|
mdel := TSQLModel.Create([TSQLRecordPeopleSimple],'test/');
|
|
Check(mdel.Root='test');
|
|
Check(length(mdel.Info)=1);
|
|
Check(mdel.Info[0].Table=TSQLRecordPeopleSimple);
|
|
Check(mdel.Info[0].Name='PeopleSimple');
|
|
Check(length(mdel.Info[0].Prop)=6);
|
|
people := TSQLRecordPeopleSimple.Create;
|
|
try
|
|
for i := 1 to 1000 do begin
|
|
people.ID := i;
|
|
people.FirstName := IntToStr(i);
|
|
people.LastName := people.FirstName+people.FirstName;
|
|
people.YearOfBirth := i+500;
|
|
people.YearOfDeath := people.YearOfBirth+40;
|
|
js := ObjectToJSON(people);
|
|
check(js=Format('{"ID":%d,"FirstName":"%d","LastName":"%d%d",'+
|
|
'"Data":"","YearOfBirth":%d,"YearOfDeath":%d}',[i,i,i,i,i+500,i+540]));
|
|
end;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
Check(PInteger(@mdel.Info[0].SimpleFields)^=$37);
|
|
Check(PInteger(@mdel.Info[0].BlobFields)^=8);
|
|
fields := mdel.Info[0].FieldNamesToFieldBits('',false);
|
|
Check(PInteger(@fields)^=$37);
|
|
fields := mdel.Info[0].FieldNamesToFieldBits('*',false);
|
|
Check(PInteger(@fields)^=PInteger(@mdel.Info[0].AllFields)^);
|
|
fields := mdel.Info[0].FieldNamesToFieldBits('id,firstname',false);
|
|
Check(PInteger(@fields)^=3);
|
|
fields := mdel.Info[0].FieldNamesToFieldBits('RowID , firstname ',false);
|
|
Check(PInteger(@fields)^=3);
|
|
Check(mdel.Info[0].FieldBitsToFieldNames(fields)='RowID,FirstName');
|
|
fields := mdel.Info[0].FieldNamesToFieldBits('firstname,id,toto',false);
|
|
Check(PInteger(@fields)^=3);
|
|
Check(mdel.Info[0].FieldBitsToFieldNames(fields)='RowID,FirstName');
|
|
mdel.Free;
|
|
end;
|
|
|
|
|
|
{ TMain }
|
|
|
|
constructor TMain.Create;
|
|
begin
|
|
inherited;
|
|
fNested := TCollection.Create(TMainNested);
|
|
fList := TStringList.Create;
|
|
end;
|
|
|
|
destructor TMain.Destroy;
|
|
begin
|
|
fList.Free;
|
|
fNested.Free;
|
|
inherited;
|
|
end;
|
|
|
|
{ TSynCrossPlatformClient }
|
|
|
|
constructor TSynCrossPlatformClient.Create(
|
|
aAuthentication: TSQLRestServerAuthenticationClass);
|
|
begin
|
|
inherited Create;
|
|
fAuthentication := aAuthentication;
|
|
end;
|
|
|
|
destructor TSynCrossPlatformClient.Destroy;
|
|
begin
|
|
CleanUp;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TSynCrossPlatformClient.CleanUp;
|
|
begin
|
|
FreeAndNil(fClient);
|
|
check(fClient=nil);
|
|
end;
|
|
|
|
procedure TSynCrossPlatformClient.Connection;
|
|
var doremotelog: boolean;
|
|
dofilelog: boolean;
|
|
begin
|
|
doremotelog := false;
|
|
dofilelog := false;
|
|
if fAuthentication=TSQLRestServerAuthenticationDefault then begin
|
|
fClient := GetClient('localhost','User','synopse');
|
|
if dofilelog then
|
|
fClient.LogToFile(LOG_VERBOSE);
|
|
if doremotelog then
|
|
fClient.LogToRemoteServer(LOG_VERBOSE,'localhost');
|
|
end else begin
|
|
fClient := TSQLRestClientHTTP.Create('localhost',SERVER_PORT,GetModel,true);
|
|
if dofilelog then
|
|
fClient.LogToFile(LOG_VERBOSE);
|
|
if doremotelog then
|
|
fClient.LogToRemoteServer(LOG_VERBOSE,'localhost');
|
|
check(fClient.Connect);
|
|
check(fClient.ServerTimeStamp<>0);
|
|
if fAuthentication<>nil then
|
|
fClient.SetUser(fAuthentication,'User','synopse');
|
|
end;
|
|
end;
|
|
|
|
procedure TSynCrossPlatformClient.ORM;
|
|
procedure TestPeople(people: TSQLRecordPeople; var id: integer);
|
|
begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=id);
|
|
Check(people.FirstName='');
|
|
Check(people.LastName='');
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(people.Sexe=sFemale);
|
|
end;
|
|
var people: TSQLRecordPeople;
|
|
Call: TSQLRestURIParams;
|
|
i,id: integer;
|
|
list: TObjectList;
|
|
{$ifdef ISDELPHI2010}
|
|
peoples: TObjectList<TSQLRecordPeople>;
|
|
{$endif ISDELPHI2010}
|
|
begin
|
|
fClient.CallBackGet('DropTable',[],Call,TSQLRecordPeople);
|
|
Check(fClient.InternalState>0);
|
|
Check(Call.OutStatus=HTTP_SUCCESS);
|
|
people := TSQLRecordPeople.Create;
|
|
try
|
|
Check(people.InternalState=0);
|
|
for i := 1 to 200 do begin
|
|
people.FirstName := 'First'+IntToStr(i);
|
|
people.LastName := 'Last'+IntToStr(i);
|
|
people.YearOfBirth := i+1800;
|
|
people.YearOfDeath := i+1825;
|
|
people.Sexe := TPeopleSexe(i and 1);
|
|
Check(fClient.Add(people,true)=i);
|
|
Check(people.InternalState=fClient.InternalState);
|
|
end;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'','',[]);
|
|
try
|
|
Check(people.InternalState=0);
|
|
id := 0;
|
|
while people.FillOne do begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=id);
|
|
Check(people.FirstName='First'+IntToStr(id));
|
|
Check(people.LastName='Last'+IntToStr(id));
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(ord(people.Sexe)=id and 1);
|
|
end;
|
|
Check(id=200);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,
|
|
'YearOFBIRTH,Yearofdeath,id','',[]);
|
|
try
|
|
Check(people.InternalState=0);
|
|
id := 0;
|
|
while people.FillOne do
|
|
TestPeople(people,id);
|
|
Check(id=200);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
list := fClient.RetrieveList(TSQLRecordPeople,'YearOFBIRTH,Yearofdeath,id','',[]);
|
|
try
|
|
id := 0;
|
|
for i := 0 to list.Count-1 do
|
|
TestPeople(TSQLRecordPeople(list[i]),id);
|
|
Check(id=200);
|
|
finally
|
|
list.Free;
|
|
end;
|
|
{$ifdef ISDELPHI2010}
|
|
peoples := fClient.RetrieveList<TSQLRecordPeople>('YearOFBIRTH,yearofdeath,id','',[]);
|
|
try
|
|
id := 0;
|
|
for i := 0 to peoples.Count-1 do
|
|
TestPeople(peoples[i],id);
|
|
Check(id=200);
|
|
finally
|
|
peoples.Free;
|
|
end;
|
|
{$endif ISDELPHI2010}
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'',
|
|
'yearofbirth=?',[1900]);
|
|
try
|
|
Check(people.InternalState=0);
|
|
id := 0;
|
|
while people.FillOne do begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=100);
|
|
Check(people.FirstName='First100');
|
|
Check(people.LastName='Last100');
|
|
Check(people.YearOfBirth=1900);
|
|
Check(people.YearOfDeath=1925);
|
|
end;
|
|
Check(id=1);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
for i := 1 to 200 do
|
|
if i and 15=0 then
|
|
fClient.Delete(TSQLRecordPeople,i) else
|
|
if i mod 82=0 then begin
|
|
people := TSQLRecordPeople.Create;
|
|
try
|
|
id := i+1;
|
|
people.ID := i;
|
|
people.FirstName := 'First'+IntToStr(id);
|
|
people.LastName := 'Last'+IntToStr(id);
|
|
people.YearOfBirth := id+1800;
|
|
people.YearOfDeath := id+1825;
|
|
Check(people.InternalState=0);
|
|
Check(fClient.Update(people,'YEarOFBIRTH,YEarOfDeath'));
|
|
Check(people.InternalState=fClient.InternalState);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
end;
|
|
for i := 1 to 200 do begin
|
|
people := TSQLRecordPeople.Create(fClient,i);
|
|
try
|
|
if i and 15=0 then
|
|
Check(people.ID=0) else begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
if i mod 82=0 then
|
|
id := i+1 else
|
|
id := i;
|
|
Check(people.ID=i);
|
|
Check(people.FirstName='First'+IntToStr(i));
|
|
Check(people.LastName='Last'+IntToStr(i));
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(ord(people.Sexe)=i and 1);
|
|
end;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynCrossPlatformClient.ORMBatch;
|
|
var people: TSQLRecordPeople;
|
|
Call: TSQLRestURIParams;
|
|
res: TIDDynArray;
|
|
{$ifndef ISDWS}
|
|
blob: TSQLRawBlob;
|
|
{$endif}
|
|
i,id: integer;
|
|
begin
|
|
fClient.CallBackGet('DropTable',[],Call,TSQLRecordPeople);
|
|
Check(fClient.InternalState>0);
|
|
Check(Call.OutStatus=HTTP_SUCCESS);
|
|
fClient.BatchStart(TSQLRecordPeople);
|
|
people := TSQLRecordPeople.Create;
|
|
try
|
|
for i := 1 to 200 do begin
|
|
Check(people.InternalState=0);
|
|
people.FirstName := 'First'+IntToStr(i);
|
|
people.LastName := 'Last'+IntToStr(i);
|
|
people.YearOfBirth := i+1800;
|
|
people.YearOfDeath := i+1825;
|
|
people.Sexe := TPeopleSexe(i and 1);
|
|
fClient.BatchAdd(people,true);
|
|
end;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
Check(fClient.BatchSend(res)=HTTP_SUCCESS);
|
|
Check(length(res)=200);
|
|
for i := 1 to length(res) do
|
|
Check(res[i-1]=i);
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'','',[]);
|
|
try
|
|
Check(people.InternalState=0);
|
|
id := 0;
|
|
while people.FillOne do begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=id);
|
|
Check(people.FirstName='First'+IntToStr(id));
|
|
Check(people.LastName='Last'+IntToStr(id));
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(ord(people.Sexe)=id and 1);
|
|
end;
|
|
Check(id=200);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,
|
|
'YearOFBIRTH,Yearofdeath,id','',[]);
|
|
try
|
|
id := 0;
|
|
Check(people.InternalState=0);
|
|
while people.FillOne do begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=id);
|
|
Check(people.FirstName='');
|
|
Check(people.LastName='');
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(people.Sexe=sFemale);
|
|
end;
|
|
Check(id=200);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'',
|
|
'yearofbirth=?',[1900]);
|
|
try
|
|
Check(people.InternalState=0);
|
|
id := 0;
|
|
while people.FillOne do begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
inc(id);
|
|
Check(people.ID=100);
|
|
Check(people.FirstName='First100');
|
|
Check(people.LastName='Last100');
|
|
Check(people.YearOfBirth=1900);
|
|
Check(people.YearOfDeath=1925);
|
|
end;
|
|
Check(id=1);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
fClient.BatchStart(nil);
|
|
for i := 1 to 200 do
|
|
if i and 15=0 then
|
|
fClient.BatchDelete(TSQLRecordPeople,i) else
|
|
if i mod 82=0 then begin
|
|
people := TSQLRecordPeople.Create;
|
|
try
|
|
id := i+1;
|
|
people.ID := i;
|
|
people.FirstName := 'First'+IntToStr(id);
|
|
people.LastName := 'Last'+IntToStr(id);
|
|
people.YearOfBirth := id+1800;
|
|
people.YearOfDeath := id+1825;
|
|
Check(fClient.BatchUpdate(people,'YEarOFBIRTH,YEarOfDeath')>=0);
|
|
Check(people.InternalState=0);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
end;
|
|
Check(fClient.BatchSend(res)=HTTP_SUCCESS);
|
|
Check(length(res)=14);
|
|
for i := 1 to 14 do
|
|
Check(res[i-1]=HTTP_SUCCESS);
|
|
for i := 1 to 200 do begin
|
|
people := TSQLRecordPeople.Create(fClient,i);
|
|
try
|
|
if i and 15=0 then
|
|
Check(people.ID=0) else begin
|
|
Check(people.InternalState=fClient.InternalState);
|
|
if i mod 82=0 then
|
|
id := i+1 else
|
|
id := i;
|
|
Check(people.ID=i);
|
|
Check(people.FirstName='First'+IntToStr(i));
|
|
Check(people.LastName='Last'+IntToStr(i));
|
|
Check(people.YearOfBirth=id+1800);
|
|
Check(people.YearOfDeath=id+1825);
|
|
Check(ord(people.Sexe)=i and 1);
|
|
end;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
end;
|
|
{$ifndef ISDWS}
|
|
exit; // Add(..,'Data') below is buggy, but RetrieveBlob() seems fine
|
|
people := TSQLRecordPeople.Create;
|
|
try
|
|
people.FirstName := 'With';
|
|
people.LastName := 'Blob';
|
|
SetLength(blob,2);
|
|
blob[0] := 1;
|
|
blob[1] := 2;
|
|
people.Data := blob;
|
|
id := fClient.Add(people,true,false,'FirstName,LastName,Data');
|
|
Check(id=201);
|
|
Check(people.InternalState=fClient.InternalState);
|
|
blob := nil;
|
|
finally
|
|
people.Free;
|
|
end;
|
|
people := TSQLRecordPeople.Create(fClient,id);
|
|
try
|
|
Check(people.FirstName='With');
|
|
Check(people.LastName='Blob');
|
|
Check(people.Data=nil);
|
|
Check(not fClient.RetrieveBlob(TSQLRecordPeople,id,'wrongfieldname',blob));
|
|
Check(blob=nil);
|
|
Check(fClient.RetrieveBlob(TSQLRecordPeople,id,'data',blob));
|
|
Check(blob<>nil);
|
|
finally
|
|
people.Free;
|
|
end;
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TSynCrossPlatformClient.Services;
|
|
var calc: ICalculator;
|
|
i,j: integer;
|
|
sex: TPeopleSexe;
|
|
name: string;
|
|
rec: TTestCustomJSONArraySimpleArray;
|
|
const SEX_TEXT: array[0..1] of RawUTF8 = ('Miss','Mister');
|
|
begin
|
|
calc := TServiceCalculator.Create(fClient);
|
|
check(calc.InstanceImplementation=sicShared);
|
|
check(calc.ServiceName='Calculator');
|
|
for i := 1 to 200 do
|
|
check(calc.Add(i,i+1)=i*2+1);
|
|
for i := 1 to 200 do begin
|
|
sex := TPeopleSexe(i and 1);
|
|
name := 'Smith';
|
|
calc.ToText(i,'$',sex,name);
|
|
check(sex=sFemale);
|
|
check(name=format('$ %d for %s Smith',[i,SEX_TEXT[i and 1]]));
|
|
end;
|
|
Fillchar(rec,SizeOf(rec),0);
|
|
for i := 1 to 100 do begin
|
|
name := calc.RecordToText(rec);
|
|
if i=1 then
|
|
check(name='{"F":"","G":[],"H":{"H1":0,"H2":"","H3":{"H3a":false,"H3b":null}},"I":"","J":[]}');
|
|
check(length(Rec.F)=i);
|
|
for j := 1 to length(Rec.F) do
|
|
check(Rec.F[j]='!');
|
|
check(length(Rec.G)=i);
|
|
for j := 0 to high(Rec.G) do
|
|
check(Rec.G[j]=IntToStr(j+1));
|
|
check(Rec.H.H1=i);
|
|
check(length(Rec.J)=i-1);
|
|
for j := 0 to high(Rec.J) do begin
|
|
Check(Rec.J[j].J1=j);
|
|
Check(Rec.J[j].J2.D2=j);
|
|
Check(Rec.J[j].J3=TRecordEnum(j mod (ord(high(TRecordEnum))+1)));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|