diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..776d522 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +; Top-most EditorConfig file +root = true + +; 4-column space indentation +[*.cs] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.gitignore b/.gitignore index 409184b..558766c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,16 @@ -.svn \ No newline at end of file +.svn/ +.vs/ +*/obj/ +*/bin/ +**/packages/ +QBFunctionTest/TestConfigs.xml +TestResults/ +*.CodeAnalysisLog.xml +*.ruleset +*.lastcodeanalysissucceeded +*.suo +*.cache +*.orig +*.bak +/License.log +/Intuit.QuickBase.sln.DotSettings.user diff --git a/Intuit.QuickBase.Client/ComparisonOperator.cs b/Intuit.QuickBase.Client/ComparisonOperator.cs index 7d5523a..7d0c24f 100644 --- a/Intuit.QuickBase.Client/ComparisonOperator.cs +++ b/Intuit.QuickBase.Client/ComparisonOperator.cs @@ -68,6 +68,14 @@ public enum ComparisonOperator /// /// Is greater than or equal to /// - GTE + GTE, + /// + /// Is in range + /// + IR, + /// + /// Is not in range + /// + XIR } } \ No newline at end of file diff --git a/Intuit.QuickBase.Client/IQApplication.cs b/Intuit.QuickBase.Client/IQApplication.cs index 2b8c9a0..1b43931 100644 --- a/Intuit.QuickBase.Client/IQApplication.cs +++ b/Intuit.QuickBase.Client/IQApplication.cs @@ -6,7 +6,7 @@ * http://www.opensource.org/licenses/eclipse-1.0.php */ using System.Collections.Generic; -using System.Xml.XPath; +using System.Xml.Linq; namespace Intuit.QuickBase.Client { @@ -18,7 +18,7 @@ public interface IQApplication IQClient Client { get; } void Disconnect(); AppInfo GetApplicationInfo(); - XPathDocument GetApplicationSchema(); + XElement GetApplicationSchema(); string CloneApplication(string qbNewName, string qbNewDescription, CloneData cloneData); void RenameApplication(string qbNewName); void DeleteApplication(); diff --git a/Intuit.QuickBase.Client/IQColumn.cs b/Intuit.QuickBase.Client/IQColumn.cs index 12f3980..d53b4fa 100644 --- a/Intuit.QuickBase.Client/IQColumn.cs +++ b/Intuit.QuickBase.Client/IQColumn.cs @@ -5,6 +5,10 @@ * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/eclipse-1.0.php */ + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.AccessControl; using Intuit.QuickBase.Core; namespace Intuit.QuickBase.Client @@ -14,9 +18,26 @@ public interface IQColumn int ColumnId { get; set; } string ColumnName { get; set; } FieldType ColumnType { get; set; } + string ColumnRole { get; set; } + bool ColumnVirtual { get; set; } + bool ColumnSummary { get; set; } + bool IsHidden { get; set; } + bool ColumnLookup { get; set; } + bool AllowHTML { get; set; } + bool CanAddChoices { get; set; } + string CurrencySymbol { get; set; } bool Equals(IQColumn column); bool Equals(object obj); int GetHashCode(); string ToString(); + List GetChoices(); + void AddChoice(object obj); + } + + internal interface IQColumn_int + { + void AcceptChanges(IQApplication Application, string tbid); + void AddChoice(object obj, bool onServer); + Dictionary GetComposites(); } } diff --git a/Intuit.QuickBase.Client/IQRecord.cs b/Intuit.QuickBase.Client/IQRecord.cs index 022d44a..99c27a4 100644 --- a/Intuit.QuickBase.Client/IQRecord.cs +++ b/Intuit.QuickBase.Client/IQRecord.cs @@ -11,15 +11,25 @@ public interface IQRecord { int RecordId { get; } RecordState RecordState { get; } + bool UncleanState { get; } bool IsOnServer { get; } - string this[int index] { get; set; } - string this[string columnName] { get; set; } + object this[int index] { get; set; } + object this[string columnName] { get; set; } void AcceptChanges(); + void UploadFile(string columnName, string filePath); void DownloadFile(string columnName, string path, int versionId); void ChangeOwnerTo(string newOwner); + string GetAsCSV(string clist); bool Equals(IQRecord record); bool Equals(object obj); int GetHashCode(); string ToString(); } + + internal interface IQRecord_int + { + void ForceUpdateState(int recId); + void ForceUpdateState(); + int GetColumnIndex(string colName); + } } diff --git a/Intuit.QuickBase.Client/IQTable.cs b/Intuit.QuickBase.Client/IQTable.cs index 23bd74a..5c595c4 100644 --- a/Intuit.QuickBase.Client/IQTable.cs +++ b/Intuit.QuickBase.Client/IQTable.cs @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/eclipse-1.0.php */ -using System.Xml.XPath; +using System.Xml.Linq; namespace Intuit.QuickBase.Client { @@ -13,27 +13,39 @@ public interface IQTable { string TableName { get; } string TableId { get; } + int KeyFID { get; } + int KeyCIdx { get; } QRecordCollection Records { get; } QColumnCollection Columns { get; } void Clear(); + string GenCsv(); string GenCsv(int queryId); - XPathDocument GetTableSchema(); + string GenCsv(Query query); + string GenHtml(string options = "", string colList = "a"); + string GenHtml(int queryId, string options = "", string colList = "a"); + string GenHtml(Query query, string options = "", string colList = "a"); + XElement GetTableSchema(); TableInfo GetTableInfo(); int GetServerRecordCount(); - void Query(); - void Query(int[] clist); - void Query(int[] clist, string options); - void Query(Query query); - void Query(Query query, int[] clist); - void Query(Query query, int[] clist, int[] slist); - void Query(Query query, int[] clist, int[] slist, string options); - void Query(int queryId); + void Query(bool clearRecords = true); + void Query(string options, bool clearRecords = true); + void Query(int[] colList, bool clearRecords = true); + void Query(int[] colList, string options, bool clearRecords = true); + void Query(Query query, bool clearRecords = true); + void Query(Query query, string options, bool clearRecords = true); + void Query(Query query, int[] colList, bool clearRecords = true); + void Query(Query query, int[] colList, int[] sortList, bool clearRecords = true); + void Query(Query query, int[] colList, int[] sortList, string options, bool clearRecords = true); + void Query(int queryId, bool clearRecords = true); + void Query(int queryId, string options, bool clearRecords = true); int QueryCount(Query query); int QueryCount(int queryId); void PurgeRecords(); void PurgeRecords(int queryId); + void PurgeRecords(Query query); void AcceptChanges(); IQRecord NewRecord(); + void RefreshColumns(); string ToString(); } } \ No newline at end of file diff --git a/Intuit.QuickBase.Client/Intuit.QuickBase.Client.csproj b/Intuit.QuickBase.Client/Intuit.QuickBase.Client.csproj index ede0358..b478f94 100644 --- a/Intuit.QuickBase.Client/Intuit.QuickBase.Client.csproj +++ b/Intuit.QuickBase.Client/Intuit.QuickBase.Client.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -10,12 +11,21 @@ Properties Intuit.QuickBase.Client Intuit.QuickBase.Client - v3.5 + v4.8 512 SAK SAK SAK SAK + + + + + 3.5 + + + + true @@ -25,6 +35,9 @@ DEBUG;TRACE prompt 4 + false + MinimumRecommendedRules.ruleset + false pdbonly @@ -33,8 +46,11 @@ TRACE prompt 4 + + false + 3.5 @@ -64,6 +80,7 @@ + @@ -100,7 +117,22 @@ Intuit.QuickBase.Core + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/QBFunctionTest/Settings.aiis b/QBFunctionTest/Settings.aiis new file mode 100644 index 0000000..0a82f1a --- /dev/null +++ b/QBFunctionTest/Settings.aiis @@ -0,0 +1,258 @@ +{ + "__type": "ArtOfTest.WebAii.Design.UserSettings", + "__value": { + "UseHttpProxy": false, + "RecordjQueryInDescriptorsIfPossible": true, + "ShouldDeleteElement": false, + "SkipDeleteElementPrompt": false, + "HideFindExpressionWelcome": false, + "MenuHoldTime": 1.0, + "SilverlightConnectTimeout": 60000, + "BaseClassName": "BaseWebAiiTest", + "UrlRecordMode": 1, + "HighlightBorderColor": -65536, + "HighlightBorderSize": 1, + "CodeGenerationElementIdentificationType": 1, + "UrlHistory": [], + "AbsoluteDragDropRecording": true, + "ShowStoryboardHorizontalLayout": false, + "ImageScalePercentage": 75, + "SkipTranslatorsOptimization": false, + "SelectedIdentificaitonScheme": "Html", + "IdentificationSchemes": { + "Html": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationOptionsScheme", + "__value": { + "CheckFindParamUniqueness": true, + "AutoDetectTestRegions": true, + "TryAttributeCombinations": true, + "AlwaysAssertTagName": true, + "IdentificationsPerTag": { + "All Elements": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationDescriptorList", + "__value": [ + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "id" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "name" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "src" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "href" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "value" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": "alt" + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": true, + "SearchType": 1, + "AttributeName": null + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.HtmlIdentificationDescriptor", + "__value": { + "IsLocked": true, + "SearchType": 8, + "AttributeName": null + } + } + ] + } + }, + "TechnologyType": 1 + } + }, + "Silverlight": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationOptionsScheme", + "__value": { + "CheckFindParamUniqueness": true, + "AutoDetectTestRegions": true, + "TryAttributeCombinations": true, + "AlwaysAssertTagName": true, + "IdentificationsPerTag": { + "All Elements": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationDescriptorList", + "__value": [ + { + "__type": "ArtOfTest.WebAii.Design.Translation.Silverlight.SilverlightIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 0, + "AttributeName": null + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.Silverlight.SilverlightIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 5, + "AttributeName": null + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.Silverlight.SilverlightIdentificationDescriptor", + "__value": { + "IsLocked": false, + "SearchType": 1, + "AttributeName": null + } + }, + { + "__type": "ArtOfTest.WebAii.Design.Translation.Silverlight.SilverlightIdentificationDescriptor", + "__value": { + "IsLocked": true, + "SearchType": 6, + "AttributeName": null + } + } + ] + } + }, + "TechnologyType": 2 + } + }, + "Desktop": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationOptionsScheme", + "__value": { + "CheckFindParamUniqueness": true, + "AutoDetectTestRegions": true, + "TryAttributeCombinations": true, + "AlwaysAssertTagName": true, + "IdentificationsPerTag": { + "All Elements": { + "__type": "ArtOfTest.Common.Design.Translation.IdentificationDescriptorList", + "__value": [ + { + "__type": "ArtOfTest.WebAii.Design.Translation.Desktop.DesktopIdentificationDescriptor", + "__value": { + "PropertyName": "ControlTypeName", + "IsLocked": true + } + } + ] + } + }, + "TechnologyType": 8 + } + } + }, + "UnitTypeTypeGeneration": 1, + "RecorderBaseUrl": "", + "IsStoryBoardCapturingEnabled": true, + "IsElementImageCapturingEnabled": true, + "UseScreenshotCache": true, + "EnableImageSearch": true, + "UseBrowserExtension": false, + "ChromiumFirstInteractionDelay": 500, + "ClientMessageReceivedTimeout": 1000, + "FirefoxMinimumJSClickDelay": 100, + "TelerikComponentsVersion": 0, + "ScrollOnImageSearch": true, + "DisplayElementImagePreview": true, + "ElementImageThreshold": 90, + "ElementImageSearchTimeout": 15000, + "SearchByImageFirst": false, + "ElementImageSearchDelay": 300, + "SimulateRealClickByDefault": true, + "SimulateRealTypingByDefault": true, + "DefaultDropDownSelection": 2, + "QuickExecutionElementWaitTimeout": 15000, + "QuickExecutionClientReadyTimeout": 60000, + "TfsUserName": "", + "TfsPassword": "", + "TfsSkipAuthDialog": true, + "TfsDomain": "", + "RecordWpfWindowStateChanged": false, + "PromptNameOnAddElement": true, + "DefaultWPFApplication": null, + "DefaultWPFApplicationArgs": null, + "DefaultWPFApplicationWorkingFolder": null, + "DefaultDesktopApplication": null, + "DefaultDesktopApplicationArgs": null, + "DefaultDesktopApplicationWorkingFolder": null, + "UseLegacySilverlightFindLogic": false, + "ProjectGuid": "08c587fc-98b1-4508-a0fd-5e3ad5454f8c", + "SourceControlRepository": null, + "IsOnline": false, + "ProjectLanguage": 2, + "ProjectReferences": [ + "System", + "System.Core", + "ArtOfTest.WebAii, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=4fd5f65be123776c", + "ArtOfTest.WebAii.Messaging, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=4fd5f65be123776c", + "ArtOfTest.WebAii.Design, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=4fc62bbc3827ab1d", + "Telerik.WebAii.Controls.Html, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.WebAii.Controls.Xaml, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.WebAii.Controls.Xaml.Wpf, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.TestingFramework.Controls.KendoUI, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.TestingFramework.Controls.KendoUI.Angular, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.TestingFramework.Controls.TelerikUI.Blazor, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45", + "Telerik.TestStudio.Translators.Common, Version=2024.4.1329.1, Culture=neutral, PublicKeyToken=528163f3e645de45" + ], + "ScheduleServerUrl": "http://localhost:8009/", + "IsScheduleServerRemote": false, + "NotificationSettings": null, + "WebComponents": true, + "ProjectVersion": "2024.4.1321.0", + "Namespace": "QBFunctionTest", + "AssemblyName": "QBFunctionTest", + "OutputFolder": "bin", + "ExcludedFiles": [], + "InDevelopmentFiles": [], + "DisabledTranslators": [], + "SkipFindExpressionSetDataDrivenWarning": false, + "BugTrackerPersistableSettings": {}, + "ActiveBugTrackers": [], + "BugTitleMask": "{Step name} step on {Test name} test failed.", + "BugAddAttachment": true, + "BugAutoSubmit": false, + "BugDescriptionMask": "{Description}", + "SelectedBrowserOption": 0, + "SelectedResponsiveBrowserOption": 0, + "ResponsiveBrowserDevice": null, + "ResponsiveBrowserWidth": 375, + "ResponsiveBrowserHeight": 812, + "ResponsiveBrowserUserAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ActiveTheme": "Light", + "ShowStepResultDetails": false + } +} \ No newline at end of file diff --git a/QBFunctionTest/TestConfigs-example.xml b/QBFunctionTest/TestConfigs-example.xml new file mode 100644 index 0000000..cc06398 --- /dev/null +++ b/QBFunctionTest/TestConfigs-example.xml @@ -0,0 +1,8 @@ + + user@example.com + examplePassword + qbExampleAppToken + example.quickbase.com + exampleAppId + exampleTableId + diff --git a/QBFunctionTest/UnitTest1.cs b/QBFunctionTest/UnitTest1.cs new file mode 100644 index 0000000..77ffb27 --- /dev/null +++ b/QBFunctionTest/UnitTest1.cs @@ -0,0 +1,470 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using System.Linq; +using Intuit.QuickBase.Client; +using Intuit.QuickBase.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace QBFunctionTest +{ + [TestClass] + public class UnitTest1 + { + private IQApplication qbApp = null; + private readonly Dictionary qbSettings = new Dictionary(); + + + private void LoadSettings() + { + try + { + XDocument setDoc = XDocument.Load("TestConfigs.xml"); + foreach (XElement xNod in setDoc.Root.Descendants()) + { + qbSettings.Add(xNod.Attribute("name").Value, xNod.Value); + } + + } + catch (Exception ex) + { + throw new ApplicationException("Can't load TestConfigs.xml file: " + ex.Message, ex); + } + } + + public void InitConnection() + { + LoadSettings(); + var client = QuickBase.Login(qbSettings["qbUser"], qbSettings["qbPass"], qbSettings["qbSiteURL"]); + qbApp = client.Connect(qbSettings["qbAppDBID"], qbSettings["qbAppToken"]); + } + + public void InitConnection2() + { + LoadSettings(); + var client = QuickBase.Login(qbSettings["qbUser"], qbSettings["qbPass"], qbSettings["qbSiteURL"]); + qbApp = client.Connect(qbSettings["qbAppDBID2"], qbSettings["qbAppToken"]); + } + + public static readonly List multiTextOptions = new List + { + "Option1", + "Option2", + "Option3", + "Foop" + }; + + class TestRecord + { + public string textVal; + public decimal floatVal; + public bool checkboxVal; + public DateTime dateVal; + public DateTime timeStampVal; + public TimeSpan timeOfDayVal; + public decimal currencyVal; + public TimeSpan durationVal; + public string emailVal; + public string phoneVal; + public decimal percentVal; + public readonly HashSet multiTextVal; + public float ratingVal; + public string urlVal; + + public TestRecord() + { + multiTextVal = new HashSet(); + } + + public void SetupTestValues() + { + textVal = "Test string #1"; + floatVal = 3452.54m; + checkboxVal = true; + dateVal = new DateTime(2015, 04, 15); + timeStampVal = new DateTime(1970, 02, 28, 23, 55, 00, DateTimeKind.Local); + timeOfDayVal = new TimeSpan(0, 12, 34, 0); + durationVal = new TimeSpan(0, 4, 5, 6); + currencyVal = 50.50m; + emailVal = "test@example.com"; + phoneVal = "(303) 555-1212"; + percentVal = 95.5m; + urlVal = "http://www.example.com"; + ratingVal = 4.5f; + multiTextVal.Add(2); + multiTextVal.Add(3); + } + + public void Setup2ndValues() + { + textVal = "Test string #2 & \"an ampersand\"."; + floatVal = 1234.56m; + checkboxVal = false; + dateVal = new DateTime(2010, 01, 12); + timeStampVal = new DateTime(1971, 03, 24, 23, 55, 11, DateTimeKind.Local); + timeOfDayVal = new TimeSpan(0, 23, 45, 0); + durationVal = new TimeSpan(1, 2, 3, 4); + currencyVal = 25.25m; + emailVal = "test2@sample.com"; + phoneVal = "(719) 555-1212"; + percentVal = 95.5m; + urlVal = "http://www.sample.com"; + ratingVal = 3.0f; + multiTextVal.Add(1); + } + } + + [TestMethod] + public void NewMultitextTest() + { + InitConnection2(); + IQTable testTab = qbApp.GetTable(qbSettings["qbTestTable2"]); + testTab.Query(); + } + + [TestMethod] + public void UntestedTest() + { + InitConnection(); + IQTable testTab = qbApp.GetTable("bpexujk45"); + testTab.Query(); + IQRecord rec = testTab.Records[0]; + var richText = rec["RichText"]; + var multiLine = rec["MultiLine"]; + var multiChoice = rec["MultiChoice"]; + var fileAttach = rec["FileAttachment"]; + QAddress address = (QAddress)rec["Address"]; + var userList = rec["ListUser"]; + QAddress homeAddress = (QAddress)rec["HomeAddress"]; + } + + [TestMethod] + public void DeletionTest() + { + InitConnection(); + List appsLst = qbApp.GrantedDBs(); + foreach (var app in appsLst) + { + foreach (var tab in app.GrantedTables) + { + if (tab.Name == "APITestApp: APIDelTestTable") + { + IQTable tbl = qbApp.GetTable(tab.Dbid); + qbApp.DeleteTable(tbl); + break; + } + } + } + IQTable testTable = qbApp.NewTable("APIDelTestTable", "dummyRec"); + testTable.Columns.Add(new QColumn("NumberValue", FieldType.@float)); + testTable.Columns.Add(new QColumn("TextValue", FieldType.text)); + + IQRecord newRec = testTable.NewRecord(); + newRec["NumberValue"] = 0; + newRec["TextValue"] = "Zeroeth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 1; + newRec["TextValue"] = "First"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 2; + newRec["TextValue"] = "Second"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 3; + newRec["TextValue"] = "Third"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 4; + newRec["TextValue"] = "Fourth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 5; + newRec["TextValue"] = "Fifth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 6; + newRec["TextValue"] = "Sixth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 7; + newRec["TextValue"] = "Seventh"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 8; + newRec["TextValue"] = "Eighth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 9; + newRec["TextValue"] = "Ninth"; + testTable.Records.Add(newRec); + newRec = testTable.NewRecord(); + newRec["NumberValue"] = 10; + newRec["TextValue"] = "Tenth"; + testTable.Records.Add(newRec); + testTable.AcceptChanges(); + + testTable.Records.RemoveAt(10); + testTable.Records.RemoveAt(8); + testTable.Records.RemoveAt(7); + testTable.Records.RemoveAt(6); + testTable.Records.RemoveAt(3); + testTable.Records.RemoveAt(1); + testTable.AcceptChanges(); + + testTable.Query(); + Assert.AreEqual(testTable.Records.Count, 5, "Record deletion fails"); + } + + [TestMethod] + public void LargeDeleteHandling() + { + InitConnection(); + List appsLst = qbApp.GrantedDBs(); + foreach (var app in appsLst) + { + foreach (var tab in app.GrantedTables) + { + if (tab.Name == "APITestApp: APIBigDelTestTable") + { + IQTable tbl = qbApp.GetTable(tab.Dbid); + qbApp.DeleteTable(tbl); + break; + } + } + } + IQTable testTable = qbApp.NewTable("APIBigDelTestTable", "dummyRec"); + testTable.Columns.Add(new QColumn("NumberValue", FieldType.@float)); + testTable.Columns.Add(new QColumn("TextValue", FieldType.text)); + + for (int i = 1; i <= 500; i++) + { + IQRecord newRec = testTable.NewRecord(); + newRec["NumberValue"] = i; + newRec["TextValue"] = "Record " + i; + testTable.Records.Add(newRec); + } + testTable.AcceptChanges(); + testTable.Query(); + Assert.AreEqual(500, testTable.Records.Count,"Big Record creation fails"); + + List delList = new List + { + 5, + 6, + 7, + 8, + 9, + 10 + }; + Random rndSrc = new Random(); + while (delList.Count < 120) + { + int addVal = rndSrc.Next(1,500); + if (!delList.Contains(addVal)) delList.Add(addVal); + } + foreach (int i in delList) + { + testTable.Records.Remove(testTable.Records.Single(r => (decimal)r["NumberValue"] == i)); + } + Assert.AreEqual(380, testTable.Records.Count, "Deletion process fail"); + testTable.AcceptChanges(); + + testTable.Query(); + Assert.AreEqual(380, testTable.Records.Count, "Big Record deletion fails"); + } + +#if false //Turning off this test as it requires external setup... will try to make a randomly generated huge table later + [TestMethod] + public void LargeTableHandling() + { + InitConnection(); + IQTable orderTable = qbApp.GetTable(qbSettings["qbBigTable"]); + Query qry = new Query(); + QueryStrings lstQry = new QueryStrings(1, ComparisonOperator.IR, "last 60 d", + LogicalOperator.NONE); + qry.Add(lstQry); + int maxRec = 100000; + orderTable.Query(qry, string.Format("skp-10.num-{0}", maxRec)); + Assert.AreEqual(maxRec, orderTable.Records.Count); + HashSet idLst = new HashSet(); + foreach (QRecord rec in orderTable.Records) + { + string id = (string)rec["Record ID#"]; + if (idLst.Contains(id)) + Assert.Fail("Duplicate ID found!"); + else + idLst.Add(id); + } + } +#endif + + [TestMethod] + public void BasicTableOps() + { + InitConnection(); + List appsLst = qbApp.GrantedDBs(); + IQTable tbl = null; + foreach (var app in appsLst) + { + foreach (var tab in app.GrantedTables) + { + if (tab.Name == "APITestApp: APITestTable") + { + tbl = qbApp.GetTable(tab.Dbid); + } + } + } + Assert.IsNotNull(tbl,"Can't find table"); + tbl.Query(); + tbl.Records[0]["CheckboxTest"] = false; + tbl.AcceptChanges(); + } + + [TestMethod] + public void BasicCreationAndRoundTripTest() + { + InitConnection(); + List appsLst = qbApp.GrantedDBs(); + foreach (var app in appsLst) + { + foreach (var tab in app.GrantedTables) + { + if (tab.Name == "APITestApp: APITestTable") + { + IQTable tbl = qbApp.GetTable(tab.Dbid); + qbApp.DeleteTable(tbl); + break; + } + } + } + + IQTable testTable = qbApp.NewTable("APITestTable", "dummyRec"); + testTable.Columns.Add(new QColumn("TextTest", FieldType.text)); + testTable.Columns.Add(new QColumn("FloatTest", FieldType.@float)); + testTable.Columns.Add(new QColumn("CheckboxTest", FieldType.checkbox)); + testTable.Columns.Add(new QColumn("DateTest", FieldType.date)); + testTable.Columns.Add(new QColumn("TimeStampTest", FieldType.timestamp)); + testTable.Columns.Add(new QColumn("TimeOfDayTest", FieldType.timeofday)); + testTable.Columns.Add(new QColumn("DurationTest", FieldType.duration)); + testTable.Columns.Add(new QColumn("CurrencyTest", FieldType.currency)); + testTable.Columns.Add(new QColumn("PercentTest", FieldType.percent)); + testTable.Columns.Add(new QColumn("EmailTest", FieldType.email)); + testTable.Columns.Add(new QColumn("PhoneTest", FieldType.phone)); + testTable.Columns.Add(new QColumn("UrlTest", FieldType.url)); + testTable.Columns.Add(new QColumn("MultiTextTest", FieldType.multitext)); + testTable.Columns.Add(new QColumn("RatingTest", FieldType.rating)); + //testTable.Columns.Add(new QColumn("FileTest", FieldType.file)); + + foreach (string val in multiTextOptions) + { + testTable.Columns["MultiTextTest"].AddChoice(val); + } + + TestRecord exemplar = new TestRecord(); + exemplar.SetupTestValues(); + + IQRecord inRec = testTable.NewRecord(); + inRec["TextTest"] = exemplar.textVal; + inRec["FloatTest"] = exemplar.floatVal; + inRec["CheckboxTest"] = exemplar.checkboxVal; + inRec["DateTest"] = exemplar.dateVal; + inRec["TimeStampTest"] = exemplar.timeStampVal; + inRec["TimeOfDayTest"] = exemplar.timeOfDayVal; + inRec["DurationTest"] = exemplar.durationVal; + inRec["CurrencyTest"] = exemplar.currencyVal; + inRec["PercentTest"] = exemplar.percentVal; + inRec["EmailTest"] = exemplar.emailVal; + inRec["PhoneTest"] = exemplar.phoneVal; + inRec["UrlTest"] = exemplar.urlVal; + inRec["MultiTextTest"] = exemplar.multiTextVal; + inRec["RatingTest"] = exemplar.ratingVal; + + Assert.AreEqual(exemplar.textVal, inRec["TextTest"], "Strings setter fails"); + Assert.AreEqual(exemplar.floatVal, inRec["FloatTest"], "Floats setter fails"); + Assert.AreEqual(exemplar.checkboxVal, inRec["CheckboxTest"], "Checkboxes setter fails"); + Assert.AreEqual(exemplar.dateVal, inRec["DateTest"], "Dates setter fails"); + Assert.AreEqual(exemplar.timeStampVal, inRec["TimeStampTest"], "TimeStamps setter fails"); + Assert.AreEqual(exemplar.timeOfDayVal, inRec["TimeOfDayTest"], "TimeOfDays setter fails"); + Assert.AreEqual(exemplar.durationVal, inRec["DurationTest"], "Durations setter fails"); + Assert.AreEqual(exemplar.currencyVal, inRec["CurrencyTest"], "Currency setter fails"); + Assert.AreEqual(exemplar.percentVal, inRec["PercentTest"], "Percent setter fails"); + Assert.AreEqual(exemplar.emailVal, inRec["EmailTest"], "Email setter fails"); + Assert.AreEqual(exemplar.phoneVal, inRec["PhoneTest"], "Phone setter fails"); + Assert.AreEqual(exemplar.urlVal, inRec["UrlTest"], "Url setter fails"); + Assert.AreEqual(exemplar.multiTextVal, inRec["MultiTextTest"], "MultiTextSetter fails"); + Assert.AreEqual(exemplar.ratingVal, inRec["RatingTest"], "RatingSetter fails"); + testTable.Records.Add(inRec); + testTable.AcceptChanges(); + + Assert.AreEqual(exemplar.textVal, inRec["TextTest"], "Strings wrong post upload"); + Assert.AreEqual(exemplar.floatVal, inRec["FloatTest"], "Floats wrong post upload"); + Assert.AreEqual(exemplar.checkboxVal, inRec["CheckboxTest"], "Checkboxes wrong post upload"); + Assert.AreEqual(exemplar.dateVal, inRec["DateTest"], "Dates wrong post upload"); + Assert.AreEqual(exemplar.timeStampVal, inRec["TimeStampTest"], "TimeStamps wrong post upload"); + Assert.AreEqual(exemplar.timeOfDayVal, inRec["TimeOfDayTest"], "TimeOfDays wrong post upload"); + Assert.AreEqual(exemplar.durationVal, inRec["DurationTest"], "Durations wrong post upload"); + Assert.AreEqual(exemplar.currencyVal, inRec["CurrencyTest"], "Currency wrong post upload"); + Assert.AreEqual(exemplar.percentVal, inRec["PercentTest"], "Percent wrong post upload"); + Assert.AreEqual(exemplar.emailVal, inRec["EmailTest"], "Email wrong post upload"); + Assert.AreEqual(exemplar.phoneVal, inRec["PhoneTest"], "Phone wrong post upload"); + Assert.AreEqual(exemplar.urlVal, inRec["UrlTest"], "Url wrong post upload"); + Assert.IsTrue(exemplar.multiTextVal.SetEquals((HashSet)inRec["MultiTextTest"]), "MultiText wrong post upload"); + Assert.AreEqual(exemplar.ratingVal, inRec["RatingTest"], "Rating wrong post upload"); + testTable.Records.Clear(); + testTable.Query(); + + IQRecord outRec = testTable.Records[0]; + Assert.AreEqual(exemplar.textVal, outRec["TextTest"], "Strings roundtrip fail"); + Assert.AreEqual(exemplar.floatVal, outRec["FloatTest"], "Floats roundtrip fail"); + Assert.AreEqual(exemplar.checkboxVal, outRec["CheckboxTest"], "Checkboxes roundtrip fail"); + Assert.AreEqual(exemplar.dateVal, outRec["DateTest"], "Dates roundtrip fail"); + Assert.AreEqual(exemplar.timeStampVal, outRec["TimeStampTest"], "TimeStamps roundtrip fail"); + Assert.AreEqual(exemplar.timeOfDayVal, outRec["TimeOfDayTest"], "TimeOfDays roundtrip fail"); + Assert.AreEqual(exemplar.durationVal, outRec["DurationTest"], "Durations roundtrip fail"); + Assert.AreEqual(exemplar.currencyVal, outRec["CurrencyTest"], "Currencies roundtrip fail"); + Assert.AreEqual(exemplar.percentVal, outRec["PercentTest"], "Percents roundtrip fail"); + Assert.AreEqual(exemplar.emailVal, outRec["EmailTest"], "Emails roundtrip fail"); + Assert.AreEqual(exemplar.phoneVal, outRec["PhoneTest"], "Phones roundtrip fail"); + Assert.AreEqual(exemplar.urlVal, outRec["UrlTest"], "Url roundtrip fail"); + Assert.IsTrue(exemplar.multiTextVal.SetEquals((HashSet)outRec["MultiTextTest"]), "MultiText roundtrip fail"); + Assert.AreEqual(exemplar.ratingVal, outRec["RatingTest"], "Rating roundtrip fail"); + + exemplar.Setup2ndValues(); + outRec["TextTest"] = exemplar.textVal; + outRec["FloatTest"] = exemplar.floatVal; + outRec["CheckboxTest"] = exemplar.checkboxVal; + outRec["DateTest"] = exemplar.dateVal; + outRec["TimeStampTest"] = exemplar.timeStampVal; + outRec["TimeOfDayTest"] = exemplar.timeOfDayVal; + outRec["DurationTest"] = exemplar.durationVal; + outRec["CurrencyTest"] = exemplar.currencyVal; + outRec["PercentTest"] = exemplar.percentVal; + outRec["EmailTest"] = exemplar.emailVal; + outRec["PhoneTest"] = exemplar.phoneVal; + outRec["UrlTest"] = exemplar.urlVal; + outRec["MultiTextTest"] = exemplar.multiTextVal; + outRec["RatingTest"] = exemplar.ratingVal; + + testTable.AcceptChanges(); + testTable.Query(); + + IQRecord outRec2 = testTable.Records[0]; + Assert.AreEqual(exemplar.textVal, outRec2["TextTest"], "Strings update fail"); + Assert.AreEqual(exemplar.floatVal, outRec2["FloatTest"], "Floats update fail"); + Assert.AreEqual(exemplar.checkboxVal, outRec2["CheckboxTest"], "Checkboxes update fail"); + Assert.AreEqual(exemplar.dateVal, outRec2["DateTest"], "Dates update fail"); + Assert.AreEqual(exemplar.timeStampVal, outRec2["TimeStampTest"], "TimeStamps update fail"); + Assert.AreEqual(exemplar.timeOfDayVal, outRec2["TimeOfDayTest"], "TimeOfDays update fail"); + Assert.AreEqual(exemplar.durationVal, outRec2["DurationTest"], "Durations update fail"); + Assert.AreEqual(exemplar.currencyVal, outRec2["CurrencyTest"], "Currencies update fail"); + Assert.AreEqual(exemplar.percentVal, outRec2["PercentTest"], "Percents update fail"); + Assert.AreEqual(exemplar.emailVal, outRec2["EmailTest"], "Emails update fail"); + Assert.AreEqual(exemplar.phoneVal, outRec2["PhoneTest"], "Phones update fail"); + Assert.AreEqual(exemplar.urlVal, outRec2["UrlTest"], "Url update fail"); + Assert.IsTrue(exemplar.multiTextVal.SetEquals((HashSet)outRec2["MultiTextTest"]), "MultiText update fail"); + Assert.AreEqual(exemplar.ratingVal, outRec2["RatingTest"], "Rating update fail"); + } + } +} diff --git a/QBFunctionTest/app.config b/QBFunctionTest/app.config new file mode 100644 index 0000000..b09e762 --- /dev/null +++ b/QBFunctionTest/app.config @@ -0,0 +1,5 @@ + + + + + diff --git a/README.md b/README.md index 51797f8..56d428b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ QuickBase-C--SDK ================ -C# wrapper for Intuit QuickBase HTTP APIs \ No newline at end of file +C# wrapper for Intuit QuickBase HTTP APIs + +In order to run the unit tests, you will need to copy QBFunctionTest/TestConfigs-example.xml to +QBFunctionTest/TestConfigs.xml and put values for your site into it. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0824ae7 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +- Fix multiple blank line problems in text files (multiple /r/n get collapsed into one) +- Alter the way that queries load data so that LoadColumns is never needed after the table instantiation +- Implement option remove for rating and multiselect fields +- more unit tests (this is complicated by the apparent limitation of not being able to create some field types from the API)