* AppRouterController.cs: chore:

* Utils.cs:
* RoleTest.cs:
* AVQuery.cs:
* AVStatus.cs:
* AVRelation.cs:
* AVFieldNameAttribute.cs:

* AVRole.cs: chore: 测试 Role 模块
oneRain 2019-08-27 15:19:19 +08:00
parent 56bfee1732
commit 9266d6e115
8 changed files with 113 additions and 151 deletions

View File

@ -34,7 +34,7 @@ namespace LeanCloud.Storage.Internal {
} }
} }
public async Task<AppRouterState> QueryAsync(string appId) { async Task<AppRouterState> QueryAsync(string appId) {
Console.WriteLine("QueryAsync"); Console.WriteLine("QueryAsync");
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId); string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);

View File

@ -0,0 +1,27 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LeanCloud;
namespace LeanCloudTests {
public class RoleTest {
[SetUp]
public void SetUp() {
Utils.InitNorthChina(true);
}
[Test]
public async Task GetUsersFromRole() {
AVQuery<AVRole> query = new AVQuery<AVRole>();
AVRole role = await query.FirstAsync();
AVQuery<AVUser> userQuery = role.Users.Query;
IEnumerable<AVUser> users = await userQuery.FindAsync();
Assert.Greater(users.Count(), 0);
TestContext.Out.WriteLine($"count: {users.Count()}");
foreach (AVUser user in users) {
TestContext.Out.WriteLine($"{user.ObjectId}, {user.Username}");
}
}
}
}

View File

@ -4,30 +4,38 @@ using NUnit.Framework;
namespace LeanCloudTests { namespace LeanCloudTests {
public static class Utils { public static class Utils {
public static void InitNorthChina() { public static void InitNorthChina(bool master = false) {
AVClient.Initialize(new AVClient.Configuration { if (master) {
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com", "qKH9ryRagHKvXeRRVkiUiHeb");
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp", } else {
ApiServer = "https://avoscloud.com" Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com");
}); }
AVClient.HttpLog(TestContext.Out.WriteLine);
} }
public static void InitEastChina() { public static void InitEastChina(bool master = false) {
AVClient.Initialize(new AVClient.Configuration { if (master) {
ApplicationId = "4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com", "eqEp4n89h4zanWFskDDpIwL4");
ApplicationKey = "GSD6DtdgGWlWolivN4qhWtlE", } else {
ApiServer = "https://4eTwHdYh.api.lncldapi.com" Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com");
}); }
AVClient.HttpLog(TestContext.Out.WriteLine);
} }
public static void InitUS() { public static void InitUS(bool master = false) {
if (master) {
Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com", "Ahb1wdFLwMgKwEaEicHRXbCY");
} else {
Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com");
}
}
static void Init(string appId, string appKey, string apiServer, string masterKey = null) {
AVClient.Initialize(new AVClient.Configuration { AVClient.Initialize(new AVClient.Configuration {
ApplicationId = "MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", ApplicationId = appId,
ApplicationKey = "p42JUxdxb95K5G8187t5ba3l", ApplicationKey = appKey,
ApiServer = "https://MFAS1GnO.api.lncldglobal.com" MasterKey = masterKey,
ApiServer = apiServer
}); });
AVClient.UseMasterKey = !string.IsNullOrEmpty(masterKey);
AVClient.HttpLog(TestContext.Out.WriteLine); AVClient.HttpLog(TestContext.Out.WriteLine);
} }
} }

View File

@ -1,24 +1,17 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LeanCloud namespace LeanCloud {
{
/// <summary> /// <summary>
/// Specifies a field name for a property on a AVObject subclass. /// Specifies a field name for a property on a AVObject subclass.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class AVFieldNameAttribute : Attribute public sealed class AVFieldNameAttribute : Attribute {
{
/// <summary> /// <summary>
/// Constructs a new AVFieldName attribute. /// Constructs a new AVFieldName attribute.
/// </summary> /// </summary>
/// <param name="fieldName">The name of the field on the AVObject that the /// <param name="fieldName">The name of the field on the AVObject that the
/// property represents.</param> /// property represents.</param>
public AVFieldNameAttribute(string fieldName) public AVFieldNameAttribute(string fieldName) {
{
FieldName = fieldName; FieldName = fieldName;
} }

View File

@ -219,7 +219,7 @@ namespace LeanCloud
}); });
} }
public Task<T> FirstAsync(CancellationToken cancellationToken) public Task<T> FirstAsync(CancellationToken cancellationToken = default)
{ {
return FirstOrDefaultAsync(cancellationToken).OnSuccess(t => return FirstOrDefaultAsync(cancellationToken).OnSuccess(t =>
{ {

View File

@ -8,71 +8,59 @@ using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
namespace LeanCloud namespace LeanCloud {
{
/// <summary> /// <summary>
/// A common base class for AVRelations. /// A common base class for AVRelations.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public abstract class AVRelationBase : IJsonConvertible public abstract class AVRelationBase : IJsonConvertible {
{
private AVObject parent; private AVObject parent;
private string key; private string key;
private string targetClassName; private string targetClassName;
internal AVRelationBase(AVObject parent, string key) internal AVRelationBase(AVObject parent, string key) {
{
EnsureParentAndKey(parent, key); EnsureParentAndKey(parent, key);
} }
internal AVRelationBase(AVObject parent, string key, string targetClassName) internal AVRelationBase(AVObject parent, string key, string targetClassName)
: this(parent, key) : this(parent, key) {
{
this.targetClassName = targetClassName; this.targetClassName = targetClassName;
} }
internal static ObjectSubclassingController SubclassingController internal static ObjectSubclassingController SubclassingController {
{ get {
get
{
return AVPlugins.Instance.SubclassingController; return AVPlugins.Instance.SubclassingController;
} }
} }
internal void EnsureParentAndKey(AVObject parent, string key) internal void EnsureParentAndKey(AVObject parent, string key) {
{
this.parent = this.parent ?? parent; this.parent = this.parent ?? parent;
this.key = this.key ?? key; this.key = this.key ?? key;
Debug.Assert(this.parent == parent, "Relation retrieved from two different objects"); Debug.Assert(this.parent == parent, "Relation retrieved from two different objects");
Debug.Assert(this.key == key, "Relation retrieved from two different keys"); Debug.Assert(this.key == key, "Relation retrieved from two different keys");
} }
internal void Add(AVObject obj) internal void Add(AVObject obj) {
{
var change = new AVRelationOperation(new[] { obj }, null); var change = new AVRelationOperation(new[] { obj }, null);
parent.PerformOperation(key, change); parent.PerformOperation(key, change);
targetClassName = change.TargetClassName; targetClassName = change.TargetClassName;
} }
internal void Remove(AVObject obj) internal void Remove(AVObject obj) {
{
var change = new AVRelationOperation(null, new[] { obj }); var change = new AVRelationOperation(null, new[] { obj });
parent.PerformOperation(key, change); parent.PerformOperation(key, change);
targetClassName = change.TargetClassName; targetClassName = change.TargetClassName;
} }
IDictionary<string, object> IJsonConvertible.ToJSON() IDictionary<string, object> IJsonConvertible.ToJSON() {
{
return new Dictionary<string, object> { return new Dictionary<string, object> {
{ "__type", "Relation"}, { "__type", "Relation"},
{ "className", targetClassName} { "className", targetClassName}
}; };
} }
internal AVQuery<T> GetQuery<T>() where T : AVObject internal AVQuery<T> GetQuery<T>() where T : AVObject {
{ if (targetClassName != null) {
if (targetClassName != null)
{
return new AVQuery<T>(targetClassName) return new AVQuery<T>(targetClassName)
.WhereRelatedTo(parent, key); .WhereRelatedTo(parent, key);
} }
@ -82,24 +70,19 @@ namespace LeanCloud
.WhereRelatedTo(parent, key); .WhereRelatedTo(parent, key);
} }
internal AVQuery<T> GetReverseQuery<T>(T target) where T : AVObject internal AVQuery<T> GetReverseQuery<T>(T target) where T : AVObject {
{ if (target.ObjectId == null) {
if (target.ObjectId == null) throw new ArgumentNullException(nameof(target), "can not query a relation without target ObjectId.");
{
throw new ArgumentNullException("target.ObjectId", "can not query a relation without target ObjectId.");
} }
return new AVQuery<T>(parent.ClassName).WhereEqualTo(key, target); return new AVQuery<T>(parent.ClassName).WhereEqualTo(key, target);
} }
internal string TargetClassName internal string TargetClassName {
{ get {
get
{
return targetClassName; return targetClassName;
} }
set set {
{
targetClassName = value; targetClassName = value;
} }
} }
@ -109,8 +92,7 @@ namespace LeanCloud
/// </summary> /// </summary>
internal static AVRelationBase CreateRelation(AVObject parent, internal static AVRelationBase CreateRelation(AVObject parent,
string key, string key,
string targetClassName) string targetClassName) {
{
var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject); var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject);
Expression<Func<AVRelation<AVObject>>> createRelationExpr = Expression<Func<AVRelation<AVObject>>> createRelationExpr =
@ -124,8 +106,7 @@ namespace LeanCloud
} }
private static AVRelation<T> CreateRelation<T>(AVObject parent, string key, string targetClassName) private static AVRelation<T> CreateRelation<T>(AVObject parent, string key, string targetClassName)
where T : AVObject where T : AVObject {
{
return new AVRelation<T>(parent, key, targetClassName); return new AVRelation<T>(parent, key, targetClassName);
} }
} }
@ -135,8 +116,7 @@ namespace LeanCloud
/// AVRelation is associated with a particular parent and key. /// AVRelation is associated with a particular parent and key.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the child objects.</typeparam> /// <typeparam name="T">The type of the child objects.</typeparam>
public sealed class AVRelation<T> : AVRelationBase where T : AVObject public sealed class AVRelation<T> : AVRelationBase where T : AVObject {
{
internal AVRelation(AVObject parent, string key) : base(parent, key) { } internal AVRelation(AVObject parent, string key) : base(parent, key) { }
@ -147,8 +127,7 @@ namespace LeanCloud
/// Adds an object to this relation. The object must already have been saved. /// Adds an object to this relation. The object must already have been saved.
/// </summary> /// </summary>
/// <param name="obj">The object to add.</param> /// <param name="obj">The object to add.</param>
public void Add(T obj) public void Add(T obj) {
{
base.Add(obj); base.Add(obj);
} }
@ -156,18 +135,15 @@ namespace LeanCloud
/// Removes an object from this relation. The object must already have been saved. /// Removes an object from this relation. The object must already have been saved.
/// </summary> /// </summary>
/// <param name="obj">The object to remove.</param> /// <param name="obj">The object to remove.</param>
public void Remove(T obj) public void Remove(T obj) {
{
base.Remove(obj); base.Remove(obj);
} }
/// <summary> /// <summary>
/// Gets a query that can be used to query the objects in this relation. /// Gets a query that can be used to query the objects in this relation.
/// </summary> /// </summary>
public AVQuery<T> Query public AVQuery<T> Query {
{ get {
get
{
return base.GetQuery<T>(); return base.GetQuery<T>();
} }
} }

View File

@ -1,32 +1,18 @@
using LeanCloud.Storage.Internal; using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LeanCloud namespace LeanCloud {
{
/// <summary> /// <summary>
/// Represents a Role on the LeanCloud server. AVRoles represent groupings /// 角色类
/// of <see cref="AVUser"/>s for the purposes of granting permissions (e.g.
/// specifying a <see cref="AVACL"/> for a <see cref="AVObject"/>. Roles
/// are specified by their sets of child users and child roles, all of which are granted
/// any permissions that the parent role has.
///
/// Roles must have a name (that cannot be changed after creation of the role),
/// and must specify an ACL.
/// </summary> /// </summary>
[AVClassName("_Role")] [AVClassName("_Role")]
public class AVRole : AVObject public class AVRole : AVObject {
{
private static readonly Regex namePattern = new Regex("^[0-9a-zA-Z_\\- ]+$"); private static readonly Regex namePattern = new Regex("^[0-9a-zA-Z_\\- ]+$");
/// <summary> /// <summary>
/// Constructs a new AVRole. You must assign a name and ACL to the role. /// Constructs a new AVRole. You must assign a name and ACL to the role.
/// </summary> /// </summary>
public AVRole() : base() { } public AVRole() { }
/// <summary> /// <summary>
/// Constructs a new AVRole with the given name. /// Constructs a new AVRole with the given name.
@ -34,8 +20,7 @@ namespace LeanCloud
/// <param name="name">The name of the role to create.</param> /// <param name="name">The name of the role to create.</param>
/// <param name="acl">The ACL for this role. Roles must have an ACL.</param> /// <param name="acl">The ACL for this role. Roles must have an ACL.</param>
public AVRole(string name, AVACL acl) public AVRole(string name, AVACL acl)
: this() : this() {
{
Name = name; Name = name;
ACL = acl; ACL = acl;
} }
@ -44,10 +29,13 @@ namespace LeanCloud
/// Gets the name of the role. /// Gets the name of the role.
/// </summary> /// </summary>
[AVFieldName("name")] [AVFieldName("name")]
public string Name public string Name {
{ get {
get { return GetProperty<string>("Name"); } return GetProperty<string>("Name");
set { SetProperty(value, "Name"); } }
set {
SetProperty(value, "Name");
}
} }
/// <summary> /// <summary>
@ -57,9 +45,10 @@ namespace LeanCloud
/// add or remove child users from the role through this relation. /// add or remove child users from the role through this relation.
/// </summary> /// </summary>
[AVFieldName("users")] [AVFieldName("users")]
public AVRelation<AVUser> Users public AVRelation<AVUser> Users {
{ get {
get { return GetRelationProperty<AVUser>("Users"); } return GetRelationProperty<AVUser>("Users");
}
} }
/// <summary> /// <summary>
@ -69,30 +58,25 @@ namespace LeanCloud
/// add or remove child roles from the role through this relation. /// add or remove child roles from the role through this relation.
/// </summary> /// </summary>
[AVFieldName("roles")] [AVFieldName("roles")]
public AVRelation<AVRole> Roles public AVRelation<AVRole> Roles {
{ get {
get { return GetRelationProperty<AVRole>("Roles"); } return GetRelationProperty<AVRole>("Roles");
}
} }
internal override void OnSettingValue(ref string key, ref object value) internal override void OnSettingValue(ref string key, ref object value) {
{
base.OnSettingValue(ref key, ref value); base.OnSettingValue(ref key, ref value);
if (key == "name") if (key == "name") {
{ if (ObjectId != null) {
if (ObjectId != null)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"A role's name can only be set before it has been saved."); "A role's name can only be set before it has been saved.");
} }
if (!(value is string)) if (!(value is string)) {
{ throw new ArgumentException("A role's name must be a string.", nameof(value));
throw new ArgumentException("A role's name must be a string.", "value");
} }
if (!namePattern.IsMatch((string)value)) if (!namePattern.IsMatch((string)value)) {
{
throw new ArgumentException( throw new ArgumentException(
"A role's name can only contain alphanumeric characters, _, -, and spaces.", "A role's name can only contain alphanumeric characters, _, -, and spaces.", nameof(value));
"value");
} }
} }
} }
@ -100,10 +84,8 @@ namespace LeanCloud
/// <summary> /// <summary>
/// Gets a <see cref="AVQuery{AVRole}"/> over the Role collection. /// Gets a <see cref="AVQuery{AVRole}"/> over the Role collection.
/// </summary> /// </summary>
public static AVQuery<AVRole> Query public static AVQuery<AVRole> Query {
{ get {
get
{
return new AVQuery<AVRole>(); return new AVQuery<AVRole>();
} }
} }

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LeanCloud
{
/// <summary>
/// 事件流系统中的一条状态
/// </summary>
[AVClassName("_Status")]
public class AVStatus : AVObject
{
private static readonly HashSet<string> readOnlyKeys = new HashSet<string> {
"messageId", "inboxType", "data","Source"
};
protected override bool IsKeyMutable(string key)
{
return !readOnlyKeys.Contains(key);
}
}
}