-
Notifications
You must be signed in to change notification settings - Fork 3
Expand Cosmos Emulator tests and harden read extension validation #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c327a5d
d921eca
527ea36
f98ebe5
67c9905
44baaef
e177988
7d70871
660a0cc
771f88d
f9d3251
6d5d59f
4a47672
0bf340c
03795eb
31d54c0
d63fbff
60e97b6
e1305d3
5d79634
f62634d
cd491da
d53c998
829a053
a06c43a
5db799c
842d5ed
bde1f0d
7badc2f
69776da
1a73340
b9c6eae
985d3f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,13 +70,22 @@ module Operations = | |
|
|
||
| type ItemRequestOptions with | ||
|
|
||
| /// <summary> | ||
| /// Adds a pre-trigger to request options. | ||
| /// </summary> | ||
| /// <param name="trigger">Trigger name.</param> | ||
| member options.AddPreTrigger (trigger : string) = | ||
| options.PreTriggers <- [| | ||
| if not <| isNull options.PreTriggers then | ||
| yield! options.PreTriggers | ||
| yield trigger | ||
| |] | ||
|
|
||
| /// <summary> | ||
| /// Adds pre-triggers to request options. | ||
| /// </summary> | ||
| /// <param name="triggers">Trigger names.</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when <paramref name="triggers"/> is <c>null</c>.</exception> | ||
| member options.AddPreTriggers (triggers : string seq) = | ||
| if obj.ReferenceEquals (triggers, null) then | ||
| raise (ArgumentNullException (nameof triggers)) | ||
|
|
@@ -93,6 +102,11 @@ module Operations = | |
| yield trigger | ||
| |] | ||
|
|
||
| /// <summary> | ||
| /// Adds post-triggers to request options. | ||
| /// </summary> | ||
| /// <param name="triggers">Trigger names.</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when <paramref name="triggers"/> is <c>null</c>.</exception> | ||
| member options.AddPostTriggers (triggers : string seq) = | ||
| if obj.ReferenceEquals (triggers, null) then | ||
| raise (ArgumentNullException (nameof triggers)) | ||
|
|
@@ -211,25 +225,55 @@ module Operations = | |
| /// <summary> | ||
| /// Checks if an item with specified Id exists in the container partition with specified key. | ||
| /// </summary> | ||
| /// <param name="deletedFieldName">Deleted marker field name.</param> | ||
| /// <param name="id">Item Id</param> | ||
| /// <param name="partitionKey">Partition key</param> | ||
| /// <param name="requestOptions">Request options</param> | ||
| /// <param name="cancellationToken">Cancellation token</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when <paramref name="deletedFieldName"/> is <c>null</c>.</exception> | ||
| /// <exception cref="ArgumentException"> | ||
| /// Thrown when <paramref name="deletedFieldName"/> does not start with a letter or underscore, | ||
| /// or contains characters other than letters, digits, or underscores. | ||
| /// </exception> | ||
| member container.IsNotDeletedAsync | ||
| deletedFieldName | ||
| (id : string, [<Optional>] requiestOptions : QueryRequestOptions, [<Optional>] cancellationToken : CancellationToken) | ||
| (deletedFieldName : string) | ||
| (id : string, [<Optional>] requestOptions : QueryRequestOptions, [<Optional>] cancellationToken : CancellationToken) | ||
| = | ||
| if obj.ReferenceEquals (deletedFieldName, null) then | ||
| nullArg (nameof deletedFieldName) | ||
|
|
||
| task { | ||
| let isAsciiLetter c = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') | ||
| let isAsciiDigit c = '0' <= c && c <= '9' | ||
|
|
||
| let isValidDeletedFieldName = | ||
| if String.IsNullOrWhiteSpace deletedFieldName then | ||
| false | ||
| else | ||
| let firstCharacter = deletedFieldName[0] | ||
| let hasValidStart = firstCharacter = '_' || isAsciiLetter firstCharacter | ||
| let hasValidBody = | ||
| deletedFieldName | ||
| |> Seq.forall (fun c -> c = '_' || isAsciiLetter c || isAsciiDigit c) | ||
|
|
||
| hasValidStart && hasValidBody | ||
|
|
||
| if not isValidDeletedFieldName then | ||
| invalidArg | ||
| (nameof deletedFieldName) | ||
| "Deleted field name must start with a letter or underscore and contain only letters, digits, or underscores." | ||
|
|
||
| let query = | ||
| QueryDefinition( | ||
| $"SELECT VALUE COUNT(1) \ | ||
| FROM item \ | ||
| WHERE item.id = @Id AND IS_NULL(item.{deletedFieldName})" | ||
| $"""SELECT VALUE COUNT(1) | ||
| FROM item | ||
| WHERE item.id = @Id | ||
| AND (NOT IS_DEFINED(item.{deletedFieldName}) OR IS_NULL(item.{deletedFieldName}))""" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot ты все кейсы этого условия тестами покрыл?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered in 64dd97d: added tests for null, whitespace, invalid first character, invalid body character, and valid underscore-prefixed field name paths for
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot так что с покрытием всех этих кейсов тестами?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Покрытие расширил в 985d3f9: добавлены проверки для valid-поля с цифрой в теле (до/после установки marker), а также дополнительные invalid-кейсы ( |
||
| ) | ||
| .WithParameter ("@Id", id) | ||
| let! count = | ||
| container.GetItemQueryIterator<int> ( | ||
| query, | ||
| requestOptions = getRequestOptionsWithMaxItemCount1 requiestOptions | ||
| requestOptions = getRequestOptionsWithMaxItemCount1 requestOptions | ||
| ) | ||
| |> CancellableTaskSeq.ofFeedIterator cancellationToken | ||
| |> TaskSeq.tryHead | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| namespace FSharp.Azure.Cosmos.Tests | ||
|
|
||
| open System.Runtime.InteropServices | ||
| open Microsoft.VisualStudio.TestTools.UnitTesting | ||
|
|
||
| [<AutoOpen>] | ||
| module AssertExtensions = | ||
|
|
||
| type Assert with | ||
|
|
||
| static member WantSome (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | Some some -> some | ||
| | None -> | ||
| Assert.Fail (message) | ||
| Unchecked.defaultof<_> | ||
|
|
||
| static member IsSome (value, [<Optional>] message : string | null) = Assert.WantSome (value, message) |> ignore | ||
|
|
||
| static member IsNone (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | Some _ -> Assert.Fail (message) | ||
| | None -> () | ||
|
|
||
| static member WantValueSome (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | ValueSome some -> some | ||
| | ValueNone -> | ||
| Assert.Fail (message) | ||
| Unchecked.defaultof<_> | ||
|
|
||
| static member IsValueSome (value, [<Optional>] message : string | null) = Assert.WantValueSome (value, message) |> ignore | ||
|
|
||
| static member IsValueNone (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | ValueSome _ -> Assert.Fail (message) | ||
| | ValueNone -> () | ||
|
|
||
| static member WantOk (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | Ok ok -> ok | ||
| | Error error -> | ||
| match message with | ||
| | null -> Assert.Fail (string error) | ||
| | message -> Assert.Fail ($"'{message}': {error}") | ||
| Unchecked.defaultof<_> | ||
|
|
||
| static member IsOk (value, [<Optional>] message : string | null) = Assert.WantOk (value, message) |> ignore | ||
|
|
||
| static member WantError (value, [<Optional>] message : string | null) = | ||
| match value with | ||
| | Error error -> error | ||
| | Ok value -> | ||
| match message with | ||
| | null -> Assert.Fail (string value) | ||
| | message -> Assert.Fail ($"'{message}': {value}") | ||
| Unchecked.defaultof<_> | ||
|
|
||
| static member IsError (value, [<Optional>] message : string | null) = Assert.WantError (value, message) |> ignore | ||
|
|
||
| static member inline IsDefaultOf< ^T> (value : ^T, [<Optional>] message : string) = | ||
| Assert.AreEqual (box value, box Unchecked.defaultof< ^T>, message) | ||
|
|
||
| static member inline OkEquals< ^R, 'E> (expected : ^R, actual : Result< ^R, 'E >, [<Optional>] message : string | null) = | ||
| Assert.AreEqual (box expected, box (Assert.WantOk (actual, message)), message) | ||
|
|
||
| static member inline ErrorEquals<'R, ^E> (expected : ^E, actual : Result<'R, ^E>, [<Optional>] message : string | null) = | ||
| Assert.AreEqual (box expected, box (Assert.WantError (actual, message)), message) | ||
|
|
||
| static member FailWithData<'T> ([<Optional>] message : string | null) = | ||
| Assert.Fail (message) | ||
| Unchecked.defaultof<'T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot
<exceptionXML комментарии добавь на все методы где они вылетаютThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Добавил XML
<exception>в 69776da для методов, где выбрасываются исключения:IsNotDeletedAsync,AddPreTriggers,AddPostTriggers.